diff --git a/core/src/trezor/ui/components/tt/scroll.py b/core/src/trezor/ui/components/tt/scroll.py index c0c9451c0b..2d0c12dbe0 100644 --- a/core/src/trezor/ui/components/tt/scroll.py +++ b/core/src/trezor/ui/components/tt/scroll.py @@ -15,7 +15,7 @@ if False: from ..common.text import TextContent -_PAGINATED_LINE_WIDTH = const(204) +PAGINATED_LINE_WIDTH = const(204) WAS_PAGED = object() @@ -283,7 +283,7 @@ def paginate_text( else: pages: list[ui.Component] = [] span.reset( - text, 0, font, break_words=break_words, line_width=_PAGINATED_LINE_WIDTH + text, 0, font, break_words=break_words, line_width=PAGINATED_LINE_WIDTH ) while span.has_more_content(): # advance to first line of the page @@ -295,7 +295,7 @@ def paginate_text( new_lines=False, content_offset=0, char_offset=span.start, - line_width=_PAGINATED_LINE_WIDTH, + line_width=PAGINATED_LINE_WIDTH, break_words=break_words, render_page_overflow=False, ) @@ -310,6 +310,9 @@ def paginate_text( return Paginated(pages) +PAGEBREAK = 0, "" + + def paginate_paragraphs( para: Iterable[tuple[int, str]], header: str, @@ -321,15 +324,16 @@ def paginate_paragraphs( span = Span("", 0, ui.NORMAL, break_words=break_words) lines = 0 content: list[TextContent] = [] - for font, text in para: - span.reset(text, 0, font, break_words=break_words) + for item in para: + if item is PAGEBREAK: + continue + span.reset(item[1], 0, item[0], break_words=break_words) lines += span.count_lines() # we'll need this for multipage too if content: content.append("\n") - content.append(font) - content.append(text) + content.extend(item) if lines <= TEXT_MAX_LINES: result = Text( @@ -339,19 +343,27 @@ def paginate_paragraphs( new_lines=False, break_words=break_words, ) - for font, text in para: - if len(result.content) != 0: - result.content.append("\n") - result.content.append(font) - result.content.append(text) + result.content = content return confirm(result) else: pages: list[ui.Component] = [] lines_left = 0 - for i, (font, text) in enumerate(para): + content_ctr = 0 + page: Text | None = None + for item in para: + if item is PAGEBREAK: + if page is not None: + page.max_lines -= lines_left + lines_left = 0 + continue + span.reset( - text, 0, font, break_words=break_words, line_width=_PAGINATED_LINE_WIDTH + item[1], + 0, + item[0], + break_words=break_words, + line_width=PAGINATED_LINE_WIDTH, ) while span.has_more_content(): @@ -362,9 +374,9 @@ def paginate_paragraphs( header_icon=header_icon, icon_color=icon_color, new_lines=False, - content_offset=i * 3 + 1, # font, _text_, newline + content_offset=content_ctr * 3 + 1, # font, _text_, newline char_offset=span.start, - line_width=_PAGINATED_LINE_WIDTH, + line_width=PAGINATED_LINE_WIDTH, render_page_overflow=False, break_words=break_words, ) @@ -374,5 +386,7 @@ def paginate_paragraphs( else: lines_left -= 1 + content_ctr += 1 + pages[-1] = confirm(pages[-1]) return Paginated(pages) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index 5049bb369f..52779b4e8a 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -11,7 +11,7 @@ from ..components.common import break_path_to_lines from ..components.common.confirm import is_confirmed, raise_if_cancelled from ..components.tt.button import ButtonCancel, ButtonDefault from ..components.tt.confirm import Confirm, HoldToConfirm -from ..components.tt.scroll import Paginated, paginate_paragraphs, paginate_text +from ..components.tt.scroll import Paginated, paginate_paragraphs, paginate_text, PAGINATED_LINE_WIDTH, PAGEBREAK from ..components.tt.text import Span, Text from ..constants.tt import ( MONO_ADDR_PER_LINE, @@ -577,6 +577,9 @@ async def confirm_hex( await raise_if_cancelled(interact(ctx, content, br_type, br_code)) +_SCREEN_FULL_THRESHOLD = const(2) + + # TODO keep name and value on the same page if possible async def confirm_properties( ctx: wire.GenericContext, @@ -588,12 +591,44 @@ async def confirm_properties( hold: bool = False, br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput, ) -> None: + span = Span() para = [] - for p in props: - if p[0] is not None: - para.append((ui.NORMAL, p[0])) - if p[1] is not None: - para.append((ui.BOLD, p[1])) + used_lines = 0 + for key, val in props: + span.reset(key or "", 0, ui.NORMAL, line_width=PAGINATED_LINE_WIDTH) + key_lines = span.count_lines() + span.reset(val or "", 0, ui.BOLD, line_width=PAGINATED_LINE_WIDTH) + val_lines = span.count_lines() + + remaining_lines = TEXT_MAX_LINES - used_lines + used_lines = (used_lines + key_lines + val_lines) % TEXT_MAX_LINES + + if key_lines + val_lines > remaining_lines: + if remaining_lines <= _SCREEN_FULL_THRESHOLD: + # there are only 2 remaining lines, don't try to fit and put everything + # on next page + para.append(PAGEBREAK) + used_lines = (key_lines + val_lines) % TEXT_MAX_LINES + + elif val_lines > 0 and key_lines >= remaining_lines: + # more than 2 remaining lines so try to fit something -- but won't fit + # at least one line of value + para.append(PAGEBREAK) + used_lines = (key_lines + val_lines) % TEXT_MAX_LINES + + elif key_lines + val_lines <= TEXT_MAX_LINES: + # Whole property won't fit to the page, but it will fit on a page + # by itself + para.append(PAGEBREAK) + used_lines = (key_lines + val_lines) % TEXT_MAX_LINES + + # else: + # None of the above. Continue fitting on the same page. + + if key: + para.append((ui.NORMAL, key)) + if val: + para.append((ui.BOLD, val)) content = paginate_paragraphs( para, title, icon, icon_color, confirm=HoldToConfirm if hold else Confirm )