def generate_buttons(start, end, page, pad): start = start + (_ITEMS_PER_PAGE + 1) * page - page end = min(end, (_ITEMS_PER_PAGE + 1) * (page + 1) - page) digits = list(range(start, end)) buttons = [NumButton(i, d, pad) for i, d in enumerate(digits)] area = ui.grid(_PLUS_BUTTON_POSITION + 3) plus = Button(area, str(end) + "+", ButtonMonoDark) plus.on_click = pad.on_plus area = ui.grid(_BACK_BUTTON_POSITION + 3) back = Button(area, res.load(ui.ICON_BACK), ButtonMonoDark) back.on_click = pad.on_back if len(digits) == _ITEMS_PER_PAGE: # move the tenth button to its proper place and make place for the back button buttons[-1].area = ui.grid(_PLUS_BUTTON_POSITION - 1 + 3) buttons.append(plus) if page == 0: back.disable() buttons.append(back) return buttons
def _generate_buttons(self): display.clear() # we need to clear old buttons start = self.start + (ITEMS_PER_PAGE + 1) * self.page - self.page end = min(self.end, (ITEMS_PER_PAGE + 1) * (self.page + 1) - self.page) digits = list(range(start, end)) self.buttons = [ Button(digit_area(i), str(d)) for i, d in enumerate(digits) ] if len(digits) == ITEMS_PER_PAGE: more = Button(digit_area(PLUS_BUTTON_POSITION), str(end) + "+", style=ui.BTN_KEY_DARK) self.buttons.append(more) # move the tenth button to its proper place and make place for the back button self.buttons[BACK_BUTTON_POSITION].area = digit_area( BACK_BUTTON_POSITION + 1) back = Button( digit_area(BACK_BUTTON_POSITION), res.load(ui.ICON_BACK), style=ui.BTN_KEY_DARK, ) if self.page == 0: back.disable() self.buttons.append(back)
class NumInput(ui.Component): def __init__(self, count: int = 5, max_count: int = 16, min_count: int = 1) -> None: super().__init__() self.count = count self.max_count = max_count self.min_count = min_count self.minus = Button(ui.grid(3), "-") self.minus.on_click = self.on_minus # type: ignore self.plus = Button(ui.grid(5), "+") self.plus.on_click = self.on_plus # type: ignore self.text = Label(ui.grid(4), "", LABEL_CENTER, ui.BOLD) self.edit(count) def dispatch(self, event: int, x: int, y: int) -> None: self.minus.dispatch(event, x, y) self.plus.dispatch(event, x, y) self.text.dispatch(event, x, y) def on_minus(self) -> None: self.edit(self.count - 1) def on_plus(self) -> None: self.edit(self.count + 1) def edit(self, count: int) -> None: count = max(count, self.min_count) count = min(count, self.max_count) if self.count != count: self.on_change(count) self.count = count self.text.content = str(count) self.text.repaint = True if self.count == self.min_count: self.minus.disable() else: self.minus.enable() if self.count == self.max_count: self.plus.disable() else: self.plus.enable() def on_change(self, count: int) -> None: pass
class PinDialog(ui.Layout): def __init__(self, prompt, subprompt, allow_cancel=True, maxlength=9): self.maxlength = maxlength self.input = PinInput(prompt, subprompt, "") icon_confirm = res.load(ui.ICON_CONFIRM) self.confirm_button = Button(ui.grid(14), icon_confirm, ButtonConfirm) self.confirm_button.on_click = self.on_confirm icon_back = res.load(ui.ICON_BACK) self.reset_button = Button(ui.grid(12), icon_back, ButtonClear) self.reset_button.on_click = self.on_reset if allow_cancel: icon_lock = res.load(ui.ICON_LOCK) self.cancel_button = Button(ui.grid(12), icon_lock, ButtonCancel) self.cancel_button.on_click = self.on_cancel else: self.cancel_button = Button(ui.grid(12), "") self.cancel_button.disable() self.pin_buttons = [ PinButton(i, d, self) for i, d in enumerate(generate_digits()) ] def dispatch(self, event, x, y): for btn in self.pin_buttons: btn.dispatch(event, x, y) self.input.dispatch(event, x, y) self.confirm_button.dispatch(event, x, y) if self.input.pin: self.reset_button.dispatch(event, x, y) else: self.cancel_button.dispatch(event, x, y) def assign(self, pin): if len(pin) > self.maxlength: return for btn in self.pin_buttons: if len(pin) < self.maxlength: btn.enable() else: btn.disable() if pin: self.reset_button.enable() self.cancel_button.disable() else: self.reset_button.disable() self.cancel_button.enable() self.input.pin = pin self.input.repaint = True def on_reset(self): self.assign("") def on_cancel(self): raise ui.Result(CANCELLED) def on_confirm(self): raise ui.Result(self.input.pin)
class PassphraseKeyboard(ui.Widget): def __init__(self, prompt, page=1): self.prompt = Prompt(prompt) self.page = page self.input = Input(ui.grid(0, n_x=1, n_y=6), '') self.back = Button(ui.grid(12), res.load(ui.ICON_BACK), style=ui.BTN_CLEAR) self.done = Button(ui.grid(14), res.load(ui.ICON_CONFIRM), style=ui.BTN_CONFIRM) self.keys = key_buttons(KEYBOARD_KEYS[self.page]) self.pbutton = None # pending key button self.pindex = 0 # index of current pending char in pbutton def render(self): # passphrase or prompt if self.input.content: self.input.render() else: self.prompt.render() render_scrollbar(self.page) # buttons self.back.render() self.done.render() for btn in self.keys: btn.render() def touch(self, event, pos): content = self.input.content if self.back.touch(event, pos) == BTN_CLICKED: if content: # backspace, delete the last character of input self.edit(content[:-1]) return else: # cancel return CANCELLED if self.done.touch(event, pos) == BTN_CLICKED: # confirm button, return the content return content for btn in self.keys: if btn.touch(event, pos) == BTN_CLICKED: if isinstance(btn.content[0], str): # key press, add new char to input or cycle the pending button if self.pbutton is btn: index = (self.pindex + 1) % len(btn.content) content = content[:-1] + btn.content[index] else: index = 0 content += btn.content[0] else: index = 0 content += ' ' self.edit(content, btn, index) return def edit(self, content, button=None, index=0): if button and len(button.content) == 1: # one-letter buttons are never pending button = None index = 0 self.pbutton = button self.pindex = index self.input.edit(content, button is not None) if content: self.back.enable() else: self.back.disable() self.prompt.taint() async def __iter__(self): self.edit(self.input.content) # init button state while True: change = self.change_page() enter = self.enter_text() wait = loop.spawn(change, enter) result = await wait if enter in wait.finished: return result @ui.layout async def enter_text(self): timeout = loop.sleep(1000 * 1000 * 1) touch = loop.wait(io.TOUCH) wait_timeout = loop.spawn(touch, timeout) wait_touch = loop.spawn(touch) content = None while content is None: self.render() if self.pbutton is not None: wait = wait_timeout else: wait = wait_touch result = await wait if touch in wait.finished: event, *pos = result content = self.touch(event, pos) else: # disable the pending buttons self.edit(self.input.content) return content async def change_page(self): swipe = await Swipe(directions=SWIPE_HORIZONTAL) if swipe == SWIPE_LEFT: self.page = (self.page + 1) % len(KEYBOARD_KEYS) else: self.page = (self.page - 1) % len(KEYBOARD_KEYS) self.keys = key_buttons(KEYBOARD_KEYS[self.page]) self.back.taint() self.done.taint() self.input.taint() self.prompt.taint()
class PassphraseKeyboard(ui.Layout): def __init__(self, prompt: str, max_length: int, page: int = 1) -> None: super().__init__() self.prompt = Prompt(prompt) self.max_length = max_length self.page = page self.input = Input(ui.grid(0, n_x=1, n_y=6), "") self.back = Button(ui.grid(12), res.load(ui.ICON_BACK), ButtonClear) self.back.on_click = self.on_back_click # type: ignore self.back.disable() self.done = Button(ui.grid(14), res.load(ui.ICON_CONFIRM), ButtonConfirm) self.done.on_click = self.on_confirm # type: ignore self.keys = key_buttons(KEYBOARD_KEYS[self.page], self) self.pending_button: Optional[KeyButton] = None self.pending_index = 0 def dispatch(self, event: int, x: int, y: int) -> None: if self.input.text: self.input.dispatch(event, x, y) else: self.prompt.dispatch(event, x, y) self.back.dispatch(event, x, y) self.done.dispatch(event, x, y) for btn in self.keys: btn.dispatch(event, x, y) if event == ui.RENDER: render_scrollbar(self.page) def on_back_click(self) -> None: # Backspace was clicked. If we have any content in the input, let's delete # the last character. Otherwise cancel. text = self.input.text if text: self.edit(text[:-1]) else: self.on_cancel() def on_key_click(self, button: KeyButton) -> None: # Key button was clicked. If this button is pending, let's cycle the # pending character in input. If not, let's just append the first # character. button_text = button.get_text_content() if self.pending_button is button: index = (self.pending_index + 1) % len(button_text) prefix = self.input.text[:-1] else: index = 0 prefix = self.input.text if len(button_text) > 1: self.edit(prefix + button_text[index], button, index) else: self.edit(prefix + button_text[index]) def on_timeout(self) -> None: # Timeout occurred, let's just reset the pending marker. self.edit(self.input.text) def edit(self, text: str, button: KeyButton = None, index: int = 0) -> None: if len(text) > self.max_length: return self.pending_button = button self.pending_index = index # modify the input state pending = button is not None self.input.edit(text, pending) if text: self.back.enable() else: self.back.disable() self.prompt.repaint = True async def handle_input(self) -> None: touch = loop.wait(io.TOUCH) timeout = loop.sleep(1000) race_touch = loop.race(touch) race_timeout = loop.race(touch, timeout) while True: if self.pending_button is not None: race = race_timeout else: race = race_touch result = await race if touch in race.finished: event, x, y = result workflow.idle_timer.touch() self.dispatch(event, x, y) else: self.on_timeout() async def handle_paging(self) -> None: swipe = await Swipe(SWIPE_HORIZONTAL) if swipe == SWIPE_LEFT: self.page = (self.page + 1) % len(KEYBOARD_KEYS) else: self.page = (self.page - 1) % len(KEYBOARD_KEYS) self.keys = key_buttons(KEYBOARD_KEYS[self.page], self) self.back.repaint = True self.done.repaint = True self.input.repaint = True self.prompt.repaint = True def on_cancel(self) -> None: raise ui.Result(CANCELLED) def on_confirm(self) -> None: raise ui.Result(self.input.text) def create_tasks(self) -> Tuple[loop.Task, ...]: tasks: Tuple[loop.Task, ...] = ( self.handle_input(), self.handle_rendering(), self.handle_paging(), ) if __debug__: from apps.debug import input_signal return tasks + (input_signal(), ) else: return tasks
class PinDialog(ui.Layout): def __init__( self, prompt: str, subprompt: Optional[str], allow_cancel: bool = True, maxlength: int = 9, ) -> None: self.maxlength = maxlength self.input = PinInput(prompt, subprompt, "") icon_confirm = res.load(ui.ICON_CONFIRM) self.confirm_button = Button(ui.grid(14), icon_confirm, ButtonConfirm) self.confirm_button.on_click = self.on_confirm # type: ignore self.confirm_button.disable() icon_back = res.load(ui.ICON_BACK) self.reset_button = Button(ui.grid(12), icon_back, ButtonClear) self.reset_button.on_click = self.on_reset # type: ignore if allow_cancel: icon_lock = res.load( ui.ICON_CANCEL if config.is_unlocked() else ui.ICON_LOCK) self.cancel_button = Button(ui.grid(12), icon_lock, ButtonCancel) self.cancel_button.on_click = self.on_cancel # type: ignore else: self.cancel_button = Button(ui.grid(12), "") self.cancel_button.disable() self.pin_buttons = [ PinButton(i, d, self) for i, d in enumerate(generate_digits()) ] def dispatch(self, event: int, x: int, y: int) -> None: self.input.dispatch(event, x, y) if self.input.pin: self.reset_button.dispatch(event, x, y) else: self.cancel_button.dispatch(event, x, y) self.confirm_button.dispatch(event, x, y) for btn in self.pin_buttons: btn.dispatch(event, x, y) def assign(self, pin: str) -> None: if len(pin) > self.maxlength: return for btn in self.pin_buttons: if len(pin) < self.maxlength: btn.enable() else: btn.disable() if pin: self.confirm_button.enable() self.reset_button.enable() self.cancel_button.disable() else: self.confirm_button.disable() self.reset_button.disable() self.cancel_button.enable() self.input.pin = pin self.input.repaint = True def on_reset(self) -> None: self.assign("") def on_cancel(self) -> None: raise ui.Result(CANCELLED) def on_confirm(self) -> None: if self.input.pin: raise ui.Result(self.input.pin) if __debug__: def create_tasks(self) -> Tuple[loop.Task, ...]: from apps.debug import input_signal return super().create_tasks() + (input_signal(), )
class PassphraseKeyboard(ui.Layout): def __init__(self, prompt, max_length, page=1): self.prompt = Prompt(prompt) self.max_length = max_length self.page = page self.input = Input(ui.grid(0, n_x=1, n_y=6), "") self.back = Button(ui.grid(12), res.load(ui.ICON_BACK), ButtonClear) self.back.on_click = self.on_back_click self.back.disable() self.done = Button(ui.grid(14), res.load(ui.ICON_CONFIRM), ButtonConfirm) self.done.on_click = self.on_confirm self.keys = key_buttons(KEYBOARD_KEYS[self.page], self) self.pending_button = None self.pending_index = 0 def dispatch(self, event, x, y): if self.input.content: self.input.dispatch(event, x, y) else: self.prompt.dispatch(event, x, y) self.back.dispatch(event, x, y) self.done.dispatch(event, x, y) for btn in self.keys: btn.dispatch(event, x, y) if event == ui.RENDER: render_scrollbar(self.page) def on_back_click(self): # Backspace was clicked. If we have any content in the input, let's delete # the last character. Otherwise cancel. content = self.input.content if content: self.edit(content[:-1]) else: self.on_cancel() def on_key_click(self, button: KeyButton): # Key button was clicked. If this button is pending, let's cycle the # pending character in input. If not, let's just append the first # character. button_text = button.get_text_content() if self.pending_button is button: index = (self.pending_index + 1) % len(button_text) prefix = self.input.content[:-1] else: index = 0 prefix = self.input.content if len(button_text) > 1: self.edit(prefix + button_text[index], button, index) else: self.edit(prefix + button_text[index]) def on_timeout(self): # Timeout occurred, let's just reset the pending marker. self.edit(self.input.content) def edit(self, content: str, button: Button = None, index: int = 0): if len(content) > self.max_length: return self.pending_button = button self.pending_index = index # modify the input state pending = button is not None self.input.edit(content, pending) if content: self.back.enable() else: self.back.disable() self.prompt.repaint = True async def handle_input(self): touch = loop.wait(io.TOUCH) timeout = loop.sleep(1000 * 1000 * 1) spawn_touch = loop.spawn(touch) spawn_timeout = loop.spawn(touch, timeout) while True: if self.pending_button is not None: spawn = spawn_timeout else: spawn = spawn_touch result = await spawn if touch in spawn.finished: event, x, y = result self.dispatch(event, x, y) else: self.on_timeout() async def handle_paging(self): swipe = await Swipe(SWIPE_HORIZONTAL) if swipe == SWIPE_LEFT: self.page = (self.page + 1) % len(KEYBOARD_KEYS) else: self.page = (self.page - 1) % len(KEYBOARD_KEYS) self.keys = key_buttons(KEYBOARD_KEYS[self.page], self) self.back.repaint = True self.done.repaint = True self.input.repaint = True self.prompt.repaint = True def on_cancel(self): raise ui.Result(CANCELLED) def on_confirm(self): raise ui.Result(self.input.content) def create_tasks(self): return self.handle_input(), self.handle_rendering( ), self.handle_paging()