def boot(self) -> None: from ubinascii import hexlify from trezor import res from . import knownapps app_id = bytes( self.app_id) # could be bytearray, which doesn't have __hash__ if app_id == _BOGUS_APPID: # TODO: display a warning dialog for bogus app ids name = 'Another U2F device' icon = res.load( 'apps/fido_u2f/res/u2f_generic.toif') # TODO: warning icon elif app_id in knownapps.knownapps: name = knownapps.knownapps[app_id] try: icon = res.load('apps/fido_u2f/res/u2f_%s.toif' % name.lower().replace(' ', '_')) except Exception: icon = res.load('apps/fido_u2f/res/u2f_generic.toif') else: name = '%s...%s' % (hexlify( app_id[:4]).decode(), hexlify(app_id[-4:]).decode()) icon = res.load('apps/fido_u2f/res/u2f_generic.toif') self.app_name = name self.app_icon = icon
async def lockscreen(): from apps.common import storage label = storage.get_label() image = storage.get_homescreen() if not label: label = 'My TREZOR' if not image: image = res.load('apps/homescreen/res/bg.toif') await ui.backlight_slide(ui.BACKLIGHT_DIM) ui.display.clear() ui.display.avatar(48, 48, image, ui.TITLE_GREY, ui.BG) ui.display.text_center(ui.WIDTH // 2, 35, label, ui.BOLD, ui.TITLE_GREY, ui.BG) ui.display.bar_radius(40, 100, 160, 40, ui.TITLE_GREY, ui.BG, 4) ui.display.bar_radius(42, 102, 156, 36, ui.BG, ui.TITLE_GREY, 4) ui.display.text_center(ui.WIDTH // 2, 128, 'Locked', ui.BOLD, ui.TITLE_GREY, ui.BG) ui.display.text_center(ui.WIDTH // 2 + 10, 220, 'Tap to unlock', ui.BOLD, ui.TITLE_GREY, ui.BG) ui.display.icon(45, 202, res.load(ui.ICON_CLICK), ui.TITLE_GREY, ui.BG) await ui.backlight_slide(ui.BACKLIGHT_NORMAL) await ui.click()
async def lockscreen() -> None: label = storage.device.get_label() image = storage.device.get_homescreen() if not label: label = "My Trezor" if not image: image = res.load("apps/homescreen/res/bg.toif") ui.backlight_fade(ui.BACKLIGHT_DIM) ui.display.clear() ui.display.avatar(48, 48, image, ui.TITLE_GREY, ui.BG) ui.display.text_center(ui.WIDTH // 2, 35, label, ui.BOLD, ui.TITLE_GREY, ui.BG) ui.display.bar_radius(40, 100, 160, 40, ui.TITLE_GREY, ui.BG, 4) ui.display.bar_radius(42, 102, 156, 36, ui.BG, ui.TITLE_GREY, 4) ui.display.text_center(ui.WIDTH // 2, 128, "Locked", ui.BOLD, ui.TITLE_GREY, ui.BG) ui.display.text_center(ui.WIDTH // 2 + 10, 220, "Tap to unlock", ui.BOLD, ui.TITLE_GREY, ui.BG) ui.display.icon(45, 202, res.load(ui.ICON_CLICK), ui.TITLE_GREY, ui.BG) ui.backlight_fade(ui.BACKLIGHT_NORMAL) await ui.click()
def render(self): target = self.target_ms start = self.start_ms stop = self.stop_ms now = utime.ticks_ms() if stop is None: r = min(now - start, target) else: r = max(stop - start + (stop - now) * _SHRINK_BY, 0) if r == 0: self.start_ms = None self.stop_ms = None if r == target: s = self.active_style else: s = self.normal_style if s["icon"] is None: ui.display.loader(r, -24, s["fg-color"], s["bg-color"]) elif s["icon-fg-color"] is None: ui.display.loader(r, -24, s["fg-color"], s["bg-color"], res.load(s["icon"])) else: ui.display.loader( r, -24, s["fg-color"], s["bg-color"], res.load(s["icon"]), s["icon-fg-color"], )
def onchange(): c = dialog.cancel if matrix.pin: c.content = res.load(ui.ICON_CLEAR) else: c.content = res.load(ui.ICON_LOCK) c.taint() c.render()
class InfoConfirm(ui.Layout): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_CANCEL = res.load(ui.ICON_CANCEL) DEFAULT_CANCEL_STYLE = ButtonCancel DEFAULT_INFO = res.load( ui.ICON_CLICK) # TODO: this should be (i) icon, not click DEFAULT_INFO_STYLE = ButtonDefault def __init__( self, content: ui.Component, confirm: ButtonContent = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, cancel: ButtonContent = DEFAULT_CANCEL, cancel_style: ButtonStyleType = DEFAULT_CANCEL_STYLE, info: ButtonContent = DEFAULT_INFO, info_style: ButtonStyleType = DEFAULT_INFO_STYLE, ) -> None: super().__init__() self.content = content self.confirm = Button(ui.grid(14), confirm, confirm_style) self.confirm.on_click = self.on_confirm # type: ignore self.info = Button(ui.grid(13), info, info_style) self.info.on_click = self.on_info # type: ignore self.cancel = Button(ui.grid(12), cancel, cancel_style) self.cancel.on_click = self.on_cancel # type: ignore def dispatch(self, event: int, x: int, y: int) -> None: self.content.dispatch(event, x, y) if self.confirm is not None: self.confirm.dispatch(event, x, y) if self.cancel is not None: self.cancel.dispatch(event, x, y) if self.info is not None: self.info.dispatch(event, x, y) def on_confirm(self) -> None: raise ui.Result(CONFIRMED) def on_cancel(self) -> None: raise ui.Result(CANCELLED) def on_info(self) -> None: raise ui.Result(INFO) if __debug__: def read_content(self) -> list[str]: return self.content.read_content() def create_tasks(self) -> tuple[loop.Task, ...]: from apps.debug import confirm_signal return super().create_tasks() + (confirm_signal(), )
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 load_icon(self, rp_id_hash: bytes) -> None: from trezor import res from apps.webauthn import knownapps fido_app = knownapps.by_rp_id_hash(rp_id_hash) if fido_app is not None and fido_app.icon is not None: self.app_icon = res.load(fido_app.icon) else: self.app_icon = res.load(DEFAULT_ICON)
class Confirm(ui.Layout): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_CANCEL = res.load(ui.ICON_CANCEL) DEFAULT_CANCEL_STYLE = ButtonCancel def __init__( self, content: ui.Component, confirm: Optional[ButtonContent] = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, cancel: Optional[ButtonContent] = DEFAULT_CANCEL, cancel_style: ButtonStyleType = DEFAULT_CANCEL_STYLE, major_confirm: bool = False, ) -> None: self.content = content if confirm is not None: if cancel is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(13, cells_x=2) else: area = ui.grid(9, n_x=2) self.confirm = Button( area, confirm, confirm_style ) # type: Optional[Button] self.confirm.on_click = self.on_confirm # type: ignore else: self.confirm = None if cancel is not None: if confirm is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(12, cells_x=1) else: area = ui.grid(8, n_x=2) self.cancel = Button(area, cancel, cancel_style) # type: Optional[Button] self.cancel.on_click = self.on_cancel # type: ignore else: self.cancel = None def dispatch(self, event: int, x: int, y: int) -> None: super().dispatch(event, x, y) self.content.dispatch(event, x, y) if self.confirm is not None: self.confirm.dispatch(event, x, y) if self.cancel is not None: self.cancel.dispatch(event, x, y) def on_confirm(self) -> None: raise ui.Result(CONFIRMED) def on_cancel(self) -> None: raise ui.Result(CANCELLED)
def onchange(): c = dialog.cancel if matrix.pin: c.normal_style = ui.BTN_CLEAR['normal'] c.content = res.load(ui.ICON_BACK) else: c.normal_style = ui.BTN_CANCEL['normal'] c.content = res.load(ui.ICON_LOCK) c.taint() c.render()
async def naive_pagination(ctx, lines, title, icon=ui.ICON_RESET, icon_color=ui.ORANGE, per_page=5): from trezor.ui.confirm import CANCELLED, CONFIRMED, DEFAULT_CANCEL, DEFAULT_CONFIRM if isinstance(lines, (list, tuple)): lines = lines else: lines = list(chunks(lines, 16)) pages = paginate_lines(lines, per_page) npages = len(pages) cur_step = 0 code = ButtonRequestType.SignTx iback = res.load(ui.ICON_BACK) inext = res.load(ui.ICON_CLICK) while cur_step <= npages: text = pages[cur_step] fst_page = cur_step == 0 lst_page = cur_step + 1 >= npages cancel_btn = DEFAULT_CANCEL if fst_page else iback cancel_style = ui.BTN_CANCEL if fst_page else ui.BTN_DEFAULT confirm_btn = DEFAULT_CONFIRM if lst_page else inext confirm_style = ui.BTN_CONFIRM if lst_page else ui.BTN_DEFAULT paging = ("%d/%d" % (cur_step + 1, npages)) if npages > 1 else "" content = Text("%s %s" % (title, paging), icon, icon_color=icon_color) content.normal(*text) reaction = await tx_dialog( ctx, code, content, cancel_btn, confirm_btn, cancel_style, confirm_style, (cur_step, npages), ) if fst_page and reaction == CANCELLED: return False elif not lst_page and reaction == CONFIRMED: cur_step += 1 elif lst_page and reaction == CONFIRMED: return True elif reaction == CANCELLED: cur_step -= 1 elif reaction == CONFIRMED: cur_step += 1
class Confirm(ui.Layout): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_CANCEL = res.load(ui.ICON_CANCEL) DEFAULT_CANCEL_STYLE = ButtonCancel def __init__( self, content, confirm=DEFAULT_CONFIRM, confirm_style=DEFAULT_CONFIRM_STYLE, cancel=DEFAULT_CANCEL, cancel_style=DEFAULT_CANCEL_STYLE, major_confirm=False, ): self.content = content if confirm is not None: if cancel is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(13, cells_x=2) else: area = ui.grid(9, n_x=2) self.confirm = Button(area, confirm, confirm_style) self.confirm.on_click = self.on_confirm else: self.confirm = None if cancel is not None: if confirm is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(12, cells_x=1) else: area = ui.grid(8, n_x=2) self.cancel = Button(area, cancel, cancel_style) self.cancel.on_click = self.on_cancel else: self.cancel = None def dispatch(self, event, x, y): self.content.dispatch(event, x, y) if self.confirm is not None: self.confirm.dispatch(event, x, y) if self.cancel is not None: self.cancel.dispatch(event, x, y) def on_confirm(self): raise ui.Result(CONFIRMED) def on_cancel(self): raise ui.Result(CANCELLED)
def load_icon(self, rp_id_hash: bytes) -> None: from trezor import res from apps.webauthn.knownapps import knownapps try: namepart = knownapps[rp_id_hash]["label"].lower().replace(" ", "_") icon = res.load("apps/webauthn/res/icon_%s.toif" % namepart) except Exception as e: icon = res.load("apps/webauthn/res/icon_webauthn.toif") if __debug__: log.exception(__name__, e) self.app_icon = icon
def render(self): progress = min(utime.ticks_ms() - self.start_ticks_ms, self.target_ms) if progress == self.target_ms: s = self.active_style else: s = self.normal_style if s['icon'] is None: ui.display.loader(progress, -8, s['fg-color'], s['bg-color']) elif s['icon-fg-color'] is None: ui.display.loader(progress, -8, s['fg-color'], s['bg-color'], res.load(s['icon'])) else: ui.display.loader(progress, -8, s['fg-color'], s['bg-color'], res.load(s['icon']), s['icon-fg-color'])
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)
def on_render(self) -> None: PULSE_PERIOD = const(1200000) super().on_render() if not self.pageable.is_first(): t = ui.pulse(PULSE_PERIOD) c = ui.blend(ui.GREY, ui.DARK_GREY, t) icon = res.load(ui.ICON_SWIPE_RIGHT) ui.display.icon(18, 68, icon, c, ui.BG) if not self.pageable.is_last(): t = ui.pulse(PULSE_PERIOD, PULSE_PERIOD // 2) c = ui.blend(ui.GREY, ui.DARK_GREY, t) icon = res.load(ui.ICON_SWIPE_LEFT) ui.display.icon(205, 68, icon, c, ui.BG)
def on_render(self) -> None: if self.start_ms is None: return target = self.target_ms r = self.elapsed_ms() if r != target: s = self.normal_style else: s = self.active_style progress = r * 1000 // target # scale to 0-1000 if s.icon is None: display.loader(progress, False, self.offset_y, s.fg_color, s.bg_color) else: display.loader( progress, False, self.offset_y, s.fg_color, s.bg_color, res.load(s.icon), s.icon_fg_color, ) if (r == 0) and (self.stop_ms is not None): self.start_ms = None self.stop_ms = None self.on_start() if r == target: self.on_finish()
def on_render(self): if not self.repaint: return image = None if not storage.is_initialized(): label = "Go to trezor.io/start" else: label = storage.device.get_label() or "My Trezor" image = storage.device.get_homescreen() if not image: image = res.load("apps/homescreen/res/bg.toif") if storage.is_initialized() and storage.device.no_backup(): ui.header_error("SEEDLESS") elif storage.is_initialized() and storage.device.unfinished_backup(): ui.header_error("BACKUP FAILED!") elif storage.is_initialized() and storage.device.needs_backup(): ui.header_warning("NEEDS BACKUP!") elif storage.is_initialized() and not config.has_pin(): ui.header_warning("PIN NOT SET!") else: ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT, ui.BG) ui.display.avatar(48, 48 - 10, image, ui.WHITE, ui.BLACK) ui.display.text_center(ui.WIDTH // 2, 220, label, ui.BOLD, ui.FG, ui.BG) self.repaint = False
def render_swipe_icon() -> None: DRAW_DELAY = const(200000) icon = res.load(ui.ICON_SWIPE) t = ui.pulse(DRAW_DELAY) c = ui.blend(ui.GREY, ui.DARK_GREY, t) ui.display.icon(70, 205, icon, c, ui.BG)
def __init__( self, content: ui.Component, confirm: str = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, loader_style: LoaderStyleType = DEFAULT_LOADER_STYLE, cancel: bool = True, ): super().__init__() self.content = content self.loader = Loader(loader_style) self.loader.on_start = self._on_loader_start # type: ignore if cancel: self.confirm = Button(ui.grid(17, n_x=4, cells_x=3), confirm, confirm_style) else: self.confirm = Button(ui.grid(4, n_x=1), confirm, confirm_style) self.confirm.on_press_start = self._on_press_start # type: ignore self.confirm.on_press_end = self._on_press_end # type: ignore self.confirm.on_click = self._on_click # type: ignore self.cancel = None if cancel: self.cancel = Button( ui.grid(16, n_x=4), res.load(ui.ICON_CANCEL), ButtonAbort ) self.cancel.on_click = self.on_cancel # type: ignore
def render_content(self, s, ax, ay, aw, ah): text_style = s.text_style fg_color = s.fg_color bg_color = s.bg_color tx = ax + 16 # x-offset of the content ty = ay + ah // 2 + 8 # y-offset of the content # entered content display.text(tx, ty, self.content, text_style, fg_color, bg_color) # word suggestion suggested_word = self.word[len(self.content):] width = display.text_width(self.content, text_style) display.text(tx + width, ty, suggested_word, text_style, ui.GREY, bg_color) if self.pending: pw = display.text_width(self.content[-1:], text_style) px = tx + width - pw display.bar(px, ty + 2, pw + 1, 3, fg_color) if self.icon: ix = ax + aw - 16 * 2 iy = ty - 16 display.icon(ix, iy, res.load(self.icon), fg_color, bg_color)
def on_render(self) -> None: target = self.target_ms start = self.start_ms stop = self.stop_ms if start is None: return now = utime.ticks_ms() if stop is None: r = min(now - start, target) else: r = max(stop - start + (stop - now) * 2, 0) if r != target: s = self.normal_style else: s = self.active_style Y = const(-24) if s.icon is None: display.loader(r, False, Y, s.fg_color, s.bg_color) else: display.loader(r, False, Y, s.fg_color, s.bg_color, res.load(s.icon), s.icon_fg_color) if r == 0: self.start_ms = None self.stop_ms = None self.on_start() if r == target: self.on_finish()
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 render_content(self, s, ax, ay, aw, ah): text_style = s.text_style fg_color = s.fg_color bg_color = s.bg_color tx = ax + 16 # x-offset of the content ty = ay + ah // 2 + 8 # y-offset of the content if not self.keyboard.is_input_final(): to_display = len(self.content) * "*" if self.pending_button: to_display = (to_display[:-1] + self.pending_button.content[self.pending_index]) else: to_display = self.word display.text(tx, ty, to_display, text_style, fg_color, bg_color) if self.pending_button and not self.keyboard.is_input_final(): width = display.text_width(to_display, text_style) pw = display.text_width(self.content[-1:], text_style) px = tx + width - pw display.bar(px, ty + 2, pw + 1, 3, fg_color) if self.icon: ix = ax + aw - 16 * 2 iy = ty - 16 display.icon(ix, iy, res.load(self.icon), fg_color, bg_color)
def render_items(self) -> None: offset_x = _CHECKLIST_OFFSET_X offset_y = TEXT_HEADER_HEIGHT + TEXT_LINE_HEIGHT bg = ui.BG for index, item in enumerate(self.items): # compute font and color if index < self.active: font = ui.BOLD fg = ui.GREEN elif index == self.active: font = ui.BOLD fg = ui.FG else: # index > self.active font = ui.NORMAL fg = ui.GREY # render item icon in past items if index < self.active: icon = res.load(ui.ICON_CONFIRM) ui.display.icon(0, offset_y - 14, icon, fg, bg) # render item text if isinstance(item, str): ui.display.text(offset_x, offset_y, item, font, fg, bg) offset_y += TEXT_LINE_HEIGHT else: for line in item: ui.display.text(offset_x, offset_y, line, font, fg, bg) offset_y += TEXT_LINE_HEIGHT
def display_homescreen() -> None: image = None if storage.slip39.is_in_progress(): label = "Waiting for other shares" elif not storage.is_initialized(): label = "Go to trezor.io/start" else: label = storage.device.get_label() or "My Trezor" image = storage.device.get_homescreen() if not image: image = res.load("apps/homescreen/res/bg.toif") if storage.is_initialized() and storage.device.no_backup(): _err("SEEDLESS") elif storage.is_initialized() and storage.device.unfinished_backup(): _err("BACKUP FAILED!") elif storage.is_initialized() and storage.device.needs_backup(): _warn("NEEDS BACKUP!") elif storage.is_initialized() and not config.has_pin(): _warn("PIN NOT SET!") elif storage.slip39.is_in_progress(): _warn("SHAMIR IN PROGRESS!") else: ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT, ui.BG) ui.display.avatar(48, 48 - 10, image, ui.WHITE, ui.BLACK) ui.display.text_center(ui.WIDTH // 2, 220, label, ui.BOLD, ui.FG, ui.BG)
def render_swipe_icon() -> None: PULSE_PERIOD = const(1200000) icon = res.load(ui.ICON_SWIPE) t = ui.pulse(PULSE_PERIOD) c = ui.blend(ui.GREY, ui.DARK_GREY, t) ui.display.icon(70, 205, icon, c, ui.BG)
def display_homescreen(): if not storage.is_initialized(): label = 'Go to trezor.io/start' image = None else: label = storage.get_label() or 'My TREZOR' image = storage.get_homescreen() if not image: image = res.load('apps/homescreen/res/bg.toif') if storage.is_initialized() and storage.unfinished_backup(): ui.display.bar(0, 0, ui.WIDTH, 30, ui.RED) ui.display.text_center(ui.WIDTH // 2, 22, 'BACKUP FAILED!', ui.BOLD, ui.WHITE, ui.RED) ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG) elif storage.is_initialized() and storage.needs_backup(): ui.display.bar(0, 0, ui.WIDTH, 30, ui.YELLOW) ui.display.text_center(ui.WIDTH // 2, 22, 'NEEDS BACKUP!', ui.BOLD, ui.BLACK, ui.YELLOW) ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG) elif storage.is_initialized() and not config.has_pin(): ui.display.bar(0, 0, ui.WIDTH, 30, ui.YELLOW) ui.display.text_center(ui.WIDTH // 2, 22, 'PIN NOT SET!', ui.BOLD, ui.BLACK, ui.YELLOW) ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG) else: ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT, ui.BG) ui.display.avatar(48, 48 - 10, image, ui.WHITE, ui.BLACK) ui.display.text_center(ui.WIDTH // 2, 220, label, ui.BOLD, ui.FG, ui.BG)
def render_content(self, s, ax, ay, aw, ah): text_style = s['text-style'] fg_color = s['fg-color'] bg_color = s['bg-color'] p = self.pending # should we draw the pending marker? t = self.content # input content w = self.word[len(t):] # suggested word i = self.icon # rendered icon tx = ax + 24 # x-offset of the content ty = ay + ah // 2 + 8 # y-offset of the content # input content and the suggested word display.text(tx, ty, t, text_style, fg_color, bg_color) width = display.text_width(t, text_style) display.text(tx + width, ty, w, text_style, ui.GREY, bg_color) if p: # pending marker pw = display.text_width(t[-1:], text_style) px = tx + width - pw display.bar(px, ty + 2, pw + 1, 3, fg_color) if i: # icon ix = ax + aw - ICON * 2 iy = ty - ICON display.icon(ix, iy, res.load(i), fg_color, bg_color)
def render_content(self, s, ax, ay, aw, ah): text_style = s.text_style fg_color = s.fg_color bg_color = s.bg_color p = self.pending # should we draw the pending marker? t = self.content # input content w = self.word[len(t):] # suggested word i = self.icon # rendered icon if not t: # render prompt display.text(20, 40, self.prompt, ui.BOLD, ui.GREY, ui.BG) return tx = ax + 24 # x-offset of the content ty = ay + ah // 2 + 8 # y-offset of the content # input content and the suggested word display.text(tx, ty, t, text_style, fg_color, bg_color) width = display.text_width(t, text_style) display.text(tx + width, ty, w, text_style, ui.GREY, bg_color) if p: # pending marker pw = display.text_width(t[-1:], text_style) px = tx + width - pw display.bar(px, ty + 2, pw + 1, 3, fg_color) if i: # icon ix = ax + aw - 16 * 2 iy = ty - 16 display.icon(ix, iy, res.load(i), fg_color, bg_color)