mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-22 05:10:56 +00:00
refactor(core): improve render_text behavior
* use less memory due to copy-less rendering * implement linebreaking on embedded \n
This commit is contained in:
parent
db5b65a420
commit
bbef9c650b
@ -142,7 +142,7 @@ async def show_warning_tx_staking_key_hash(
|
|||||||
|
|
||||||
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||||
page2.normal("Staking key hash:")
|
page2.normal("Staking key hash:")
|
||||||
page2.mono(*chunks(hexlify(staking_key_hash), 17))
|
page2.mono(*chunks(hexlify(staking_key_hash).decode(), 17))
|
||||||
|
|
||||||
page3 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
page3 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||||
page3.normal("Change amount:")
|
page3.normal("Change amount:")
|
||||||
|
@ -64,7 +64,7 @@ async def _request_on_host(ctx: wire.Context) -> str:
|
|||||||
text.normal("the passphrase!")
|
text.normal("the passphrase!")
|
||||||
await require_confirm(ctx, text, ButtonRequestType.Other)
|
await require_confirm(ctx, text, ButtonRequestType.Other)
|
||||||
|
|
||||||
text = Text("Hidden wallet", ICON_CONFIG)
|
text = Text("Hidden wallet", ICON_CONFIG, break_words=True)
|
||||||
text.normal("Use this passphrase?")
|
text.normal("Use this passphrase?")
|
||||||
text.br()
|
text.br()
|
||||||
text.mono(ack.passphrase)
|
text.mono(ack.passphrase)
|
||||||
|
@ -59,13 +59,13 @@ class InfoConfirm(ui.Layout):
|
|||||||
ui.display.bar_radius(x, y, w, h, bg_color, ui.BG, ui.RADIUS)
|
ui.display.bar_radius(x, y, w, h, bg_color, ui.BG, ui.RADIUS)
|
||||||
|
|
||||||
# render the info text
|
# render the info text
|
||||||
render_text( # type: ignore
|
render_text(
|
||||||
self.text,
|
self.text,
|
||||||
new_lines=False,
|
new_lines=False,
|
||||||
max_lines=6,
|
max_lines=6,
|
||||||
offset_y=y + TEXT_LINE_HEIGHT,
|
offset_y=y + TEXT_LINE_HEIGHT,
|
||||||
offset_x=x + TEXT_MARGIN_LEFT - ui.VIEWX,
|
offset_x=x + TEXT_MARGIN_LEFT - ui.VIEWX,
|
||||||
offset_x_max=x + w - ui.VIEWX,
|
line_width=w,
|
||||||
fg=fg_color,
|
fg=fg_color,
|
||||||
bg=bg_color,
|
bg=bg_color,
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ from micropython import const
|
|||||||
from trezor import ui
|
from trezor import ui
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import List, Union
|
from typing import Any, List, Optional, Union
|
||||||
|
|
||||||
TEXT_HEADER_HEIGHT = const(48)
|
TEXT_HEADER_HEIGHT = const(48)
|
||||||
TEXT_LINE_HEIGHT = const(26)
|
TEXT_LINE_HEIGHT = const(26)
|
||||||
@ -15,12 +15,164 @@ TEXT_MAX_LINES = const(5)
|
|||||||
BR = const(-256)
|
BR = const(-256)
|
||||||
BR_HALF = const(-257)
|
BR_HALF = const(-257)
|
||||||
|
|
||||||
|
_FONTS = (ui.NORMAL, ui.BOLD, ui.MONO)
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
TextContent = Union[str, int]
|
TextContent = Union[str, int]
|
||||||
|
|
||||||
|
|
||||||
|
DASH_WIDTH = ui.display.text_width("-", ui.BOLD)
|
||||||
|
|
||||||
|
|
||||||
|
class Span:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
string: str = "",
|
||||||
|
start: int = 0,
|
||||||
|
font: int = ui.NORMAL,
|
||||||
|
line_width: int = ui.WIDTH - TEXT_MARGIN_LEFT,
|
||||||
|
offset_x: int = 0,
|
||||||
|
break_words: bool = False,
|
||||||
|
) -> None:
|
||||||
|
self.reset(string, start, font, line_width, offset_x, break_words)
|
||||||
|
|
||||||
|
def reset(
|
||||||
|
self,
|
||||||
|
string: str,
|
||||||
|
start: int,
|
||||||
|
font: int,
|
||||||
|
line_width: int = ui.WIDTH - TEXT_MARGIN_LEFT,
|
||||||
|
offset_x: int = 0,
|
||||||
|
break_words: bool = False,
|
||||||
|
) -> None:
|
||||||
|
self.string = string
|
||||||
|
self.start = start
|
||||||
|
self.font = font
|
||||||
|
self.line_width = line_width
|
||||||
|
self.offset_x = offset_x
|
||||||
|
self.break_words = break_words
|
||||||
|
|
||||||
|
self.length = 0
|
||||||
|
self.width = 0
|
||||||
|
self.word_break = False
|
||||||
|
self.advance_whitespace = False
|
||||||
|
|
||||||
|
def count_lines(self) -> int:
|
||||||
|
"""Get a number of lines in the specified string.
|
||||||
|
|
||||||
|
Should be used with a cleanly reset span. Leaves the span in the final position.
|
||||||
|
"""
|
||||||
|
n_lines = 0
|
||||||
|
while self.next_line():
|
||||||
|
n_lines += 1
|
||||||
|
# deal with trailing newlines: if the final span does not have any content,
|
||||||
|
# do not count it
|
||||||
|
if self.length > 0:
|
||||||
|
n_lines += 1
|
||||||
|
return n_lines
|
||||||
|
|
||||||
|
def has_more_content(self) -> bool:
|
||||||
|
"""Look ahead to check if there is more content after the current span is
|
||||||
|
consumed.
|
||||||
|
"""
|
||||||
|
start = self.start + self.length
|
||||||
|
if self.advance_whitespace:
|
||||||
|
start += 1
|
||||||
|
return start < len(self.string)
|
||||||
|
|
||||||
|
def next_line(self) -> bool:
|
||||||
|
"""Advance the span to point to contents of the next line.
|
||||||
|
|
||||||
|
Returns True if the renderer should make newline afterwards, False if this is
|
||||||
|
the end of the text.
|
||||||
|
|
||||||
|
Within the renderer, we use this as:
|
||||||
|
|
||||||
|
>>> while span.next_line():
|
||||||
|
>>> render_the_line(span)
|
||||||
|
>>> go_to_next_line()
|
||||||
|
>>> render_the_line(span) # final line without linebreak
|
||||||
|
|
||||||
|
This is unsuitable for other uses however. To count lines (as in
|
||||||
|
`apps.common.layout.paginate_text`), use instead:
|
||||||
|
|
||||||
|
>>> while span.has_more_content():
|
||||||
|
>>> span.next_line()
|
||||||
|
"""
|
||||||
|
# We are making copies of most class variables so that the lookup is faster.
|
||||||
|
# This also allows us to pick defaults independently of the current status
|
||||||
|
string = self.string
|
||||||
|
start = self.start + self.length
|
||||||
|
line_width = self.line_width - self.offset_x
|
||||||
|
break_words = self.break_words
|
||||||
|
font = self.font
|
||||||
|
|
||||||
|
self.offset_x = 0
|
||||||
|
width = 0
|
||||||
|
result_width = 0
|
||||||
|
length = 0
|
||||||
|
|
||||||
|
if start >= len(string):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# advance over the left-over whitespace character from last time
|
||||||
|
if self.advance_whitespace:
|
||||||
|
start += 1
|
||||||
|
|
||||||
|
word_break = True
|
||||||
|
advance_whitespace = False
|
||||||
|
for i in range(len(string) - start):
|
||||||
|
nextchar_width = ui.display.text_width(string[start + i], font)
|
||||||
|
|
||||||
|
if string[start + i] in " \n":
|
||||||
|
word_break = False
|
||||||
|
length = i # break is _before_ the whitespace
|
||||||
|
advance_whitespace = True
|
||||||
|
result_width = width
|
||||||
|
if string[start + i] == "\n":
|
||||||
|
# do not continue over newline
|
||||||
|
break
|
||||||
|
|
||||||
|
elif width + nextchar_width >= line_width:
|
||||||
|
# this char would overflow the line. end loop, use last result
|
||||||
|
break
|
||||||
|
|
||||||
|
elif (
|
||||||
|
break_words or word_break
|
||||||
|
) and width + nextchar_width + DASH_WIDTH < line_width:
|
||||||
|
# Trying a possible break in the middle of a word.
|
||||||
|
# We can do this if:
|
||||||
|
# - we haven't found a space yet (word_break is still True) -- if a word
|
||||||
|
# doesn't fit on a single line, this will place a break in it
|
||||||
|
# - we are allowed to break words (break_words is True)
|
||||||
|
# AND the current character and a word-break dash will fit on the line.
|
||||||
|
result_width = width + nextchar_width
|
||||||
|
length = i + 1 # break is _after_ current character
|
||||||
|
advance_whitespace = False
|
||||||
|
word_break = True
|
||||||
|
|
||||||
|
width += nextchar_width
|
||||||
|
|
||||||
|
else:
|
||||||
|
# whole string (from offset) fits
|
||||||
|
word_break = False
|
||||||
|
advance_whitespace = False
|
||||||
|
result_width = width
|
||||||
|
length = len(string) - start
|
||||||
|
|
||||||
|
self.start = start
|
||||||
|
self.length = length
|
||||||
|
self.width = result_width
|
||||||
|
self.word_break = word_break
|
||||||
|
self.advance_whitespace = advance_whitespace
|
||||||
|
return start + length < len(string)
|
||||||
|
|
||||||
|
|
||||||
|
_WORKING_SPAN = Span()
|
||||||
|
|
||||||
|
|
||||||
def render_text(
|
def render_text(
|
||||||
words: List[TextContent],
|
items: List[TextContent],
|
||||||
new_lines: bool,
|
new_lines: bool,
|
||||||
max_lines: int,
|
max_lines: int,
|
||||||
font: int = ui.NORMAL,
|
font: int = ui.NORMAL,
|
||||||
@ -28,96 +180,172 @@ def render_text(
|
|||||||
bg: int = ui.BG,
|
bg: int = ui.BG,
|
||||||
offset_x: int = TEXT_MARGIN_LEFT,
|
offset_x: int = TEXT_MARGIN_LEFT,
|
||||||
offset_y: int = TEXT_HEADER_HEIGHT + TEXT_LINE_HEIGHT,
|
offset_y: int = TEXT_HEADER_HEIGHT + TEXT_LINE_HEIGHT,
|
||||||
offset_x_max: int = ui.WIDTH,
|
line_width: int = ui.WIDTH - TEXT_MARGIN_LEFT,
|
||||||
|
item_offset: int = 0,
|
||||||
|
char_offset: int = 0,
|
||||||
|
break_words: bool = False,
|
||||||
|
render_page_overflow: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""Render a sequence of items on screen.
|
||||||
|
|
||||||
|
The items can either be strings, or rendering instructions specified as ints.
|
||||||
|
They can change font, insert an explicit linebreak, or change color of the following
|
||||||
|
text.
|
||||||
|
|
||||||
|
If `new_lines` is true, a linebreak is rendered after every string. In effect, the
|
||||||
|
following calls are equivalent:
|
||||||
|
|
||||||
|
>>> render_text(["hello", "world"], new_lines=True)
|
||||||
|
>>> render_text(["hello\nworld"], new_lines=False)
|
||||||
|
|
||||||
|
TODO, we should get rid of all cases that use `new_lines=True`
|
||||||
|
|
||||||
|
If the rendered text ends up longer than `max_lines`, a trailing "..." is rendered
|
||||||
|
at end. This indicates to the user that the full contents have not been shown.
|
||||||
|
It is possible to override this behavior via `render_page_overflow` argument --
|
||||||
|
if false, the trailing "..." is not shown. This is useful when the rendered text is
|
||||||
|
in fact paginated.
|
||||||
|
|
||||||
|
`font` specifies the default font, but that can be overriden by font instructions
|
||||||
|
in `items`.
|
||||||
|
`fg` specifies default foreground color, which can also be overriden by instructions
|
||||||
|
in `items`.
|
||||||
|
`bg` specifies background color. This cannot be overriden.
|
||||||
|
|
||||||
|
`offset_x` and `offset_y` specify starting XY position of the text bounding box.
|
||||||
|
`line_width` specifies width of the bounding box. Height of the bounding box is
|
||||||
|
calculated as `max_lines * TEXT_LINE_HEIGHT`.
|
||||||
|
|
||||||
|
`item_offset` and `char_offset` must be specified together. Item offset specifies
|
||||||
|
the first element of `items` which should be considered, and char offset specifies
|
||||||
|
the first character of the indicated item which should be considered.
|
||||||
|
The purpose is to allow rendering different "pages" of text, using the same `items`
|
||||||
|
argument (slicing the list could be expensive in terms of memory).
|
||||||
|
The item selected by `item_offset` must be a string.
|
||||||
|
|
||||||
|
If `break_words` is false (default), linebreaks will only be rendered (a) at
|
||||||
|
whitespace, or (b) in case a word does not fit on a single line. If true, whitespace
|
||||||
|
is ignored and linebreaks are inserted after the last character that fits.
|
||||||
|
"""
|
||||||
# initial rendering state
|
# initial rendering state
|
||||||
INITIAL_OFFSET_X = offset_x
|
INITIAL_OFFSET_X = offset_x
|
||||||
offset_y_max = TEXT_HEADER_HEIGHT + (TEXT_LINE_HEIGHT * max_lines)
|
|
||||||
|
|
||||||
FONTS = (ui.NORMAL, ui.BOLD, ui.MONO)
|
|
||||||
|
|
||||||
# sizes of common glyphs
|
|
||||||
SPACE = ui.display.text_width(" ", font)
|
SPACE = ui.display.text_width(" ", font)
|
||||||
DASH = ui.display.text_width("-", ui.BOLD)
|
offset_y_max = TEXT_HEADER_HEIGHT + (TEXT_LINE_HEIGHT * max_lines)
|
||||||
ELLIPSIS = ui.display.text_width("...", ui.BOLD)
|
span = _WORKING_SPAN
|
||||||
|
|
||||||
for word_index, word in enumerate(words):
|
for item_index in range(item_offset, len(items)):
|
||||||
has_next_word = word_index < len(words) - 1
|
# load current item
|
||||||
|
item = items[item_index]
|
||||||
|
|
||||||
if isinstance(word, int):
|
if isinstance(item, int):
|
||||||
if word is BR or word is BR_HALF:
|
if item is BR or item is BR_HALF:
|
||||||
# line break or half-line break
|
# line break or half-line break
|
||||||
if offset_y > offset_y_max:
|
if offset_y > offset_y_max:
|
||||||
ui.display.text(offset_x, offset_y, "...", ui.BOLD, ui.GREY, bg)
|
if render_page_overflow:
|
||||||
|
ui.display.text(offset_x, offset_y, "...", ui.BOLD, ui.GREY, bg)
|
||||||
return
|
return
|
||||||
offset_x = INITIAL_OFFSET_X
|
offset_x = INITIAL_OFFSET_X
|
||||||
offset_y += TEXT_LINE_HEIGHT if word is BR else TEXT_LINE_HEIGHT_HALF
|
offset_y += TEXT_LINE_HEIGHT if item is BR else TEXT_LINE_HEIGHT_HALF
|
||||||
elif word in FONTS:
|
elif item in _FONTS:
|
||||||
# change of font style
|
# change of font style
|
||||||
font = word
|
font = item
|
||||||
|
SPACE = ui.display.text_width(" ", font)
|
||||||
else:
|
else:
|
||||||
# change of foreground color
|
# change of foreground color
|
||||||
fg = word
|
fg = item
|
||||||
continue
|
continue
|
||||||
|
|
||||||
width = ui.display.text_width(word, font)
|
# XXX hack:
|
||||||
|
# if the upcoming word does not fit on this line but fits on the following,
|
||||||
while offset_x + width > offset_x_max or (
|
# render it after a linebreak
|
||||||
has_next_word and offset_y >= offset_y_max
|
item_width = ui.display.text_width(item, font)
|
||||||
|
if (
|
||||||
|
item_width <= line_width
|
||||||
|
and item_width + offset_x - INITIAL_OFFSET_X > line_width
|
||||||
|
and "\n" not in item
|
||||||
):
|
):
|
||||||
beginning_of_line = offset_x == INITIAL_OFFSET_X
|
offset_y += TEXT_LINE_HEIGHT
|
||||||
word_fits_in_one_line = width < (offset_x_max - INITIAL_OFFSET_X)
|
ui.display.text(INITIAL_OFFSET_X, offset_y, item, font, fg, bg)
|
||||||
if (
|
offset_x = INITIAL_OFFSET_X + item_width + SPACE
|
||||||
offset_y < offset_y_max
|
continue
|
||||||
and word_fits_in_one_line
|
|
||||||
and not beginning_of_line
|
span.reset(
|
||||||
):
|
item,
|
||||||
# line break
|
char_offset,
|
||||||
offset_x = INITIAL_OFFSET_X
|
font,
|
||||||
offset_y += TEXT_LINE_HEIGHT
|
line_width=line_width,
|
||||||
break
|
offset_x=offset_x - INITIAL_OFFSET_X,
|
||||||
# word split
|
break_words=break_words,
|
||||||
if offset_y < offset_y_max:
|
)
|
||||||
split = "-"
|
char_offset = 0
|
||||||
splitw = DASH
|
while span.next_line():
|
||||||
else:
|
ui.display.text(
|
||||||
split = "..."
|
offset_x, offset_y, item, font, fg, bg, span.start, span.length
|
||||||
splitw = ELLIPSIS
|
)
|
||||||
# find span that fits
|
end_of_page = offset_y >= offset_y_max
|
||||||
for index in range(len(word) - 1, 0, -1):
|
have_more_content = span.has_more_content() or item_index < len(items) - 1
|
||||||
letter = word[index]
|
|
||||||
width -= ui.display.text_width(letter, font)
|
if end_of_page and have_more_content and render_page_overflow:
|
||||||
if offset_x + width + splitw < offset_x_max:
|
ui.display.text(
|
||||||
break
|
offset_x + span.width, offset_y, "...", ui.BOLD, ui.GREY, bg
|
||||||
else:
|
)
|
||||||
index = 0
|
elif span.word_break:
|
||||||
span = word[:index]
|
ui.display.text(
|
||||||
# render word span
|
offset_x + span.width, offset_y, "-", ui.BOLD, ui.GREY, bg
|
||||||
ui.display.text(offset_x, offset_y, span, font, fg, bg)
|
)
|
||||||
ui.display.text(offset_x + width, offset_y, split, ui.BOLD, ui.GREY, bg)
|
|
||||||
# line break
|
if end_of_page:
|
||||||
if offset_y >= offset_y_max:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
offset_x = INITIAL_OFFSET_X
|
offset_x = INITIAL_OFFSET_X
|
||||||
offset_y += TEXT_LINE_HEIGHT
|
offset_y += TEXT_LINE_HEIGHT
|
||||||
# continue with the rest
|
|
||||||
word = word[index:]
|
|
||||||
width = ui.display.text_width(word, font)
|
|
||||||
|
|
||||||
# render word
|
# render last chunk
|
||||||
ui.display.text(offset_x, offset_y, word, font, fg, bg)
|
ui.display.text(offset_x, offset_y, item, font, fg, bg, span.start, span.length)
|
||||||
|
|
||||||
if new_lines and has_next_word:
|
if new_lines:
|
||||||
# line break
|
|
||||||
if offset_y >= offset_y_max:
|
|
||||||
ui.display.text(offset_x, offset_y, "...", ui.BOLD, ui.GREY, bg)
|
|
||||||
return
|
|
||||||
offset_x = INITIAL_OFFSET_X
|
offset_x = INITIAL_OFFSET_X
|
||||||
offset_y += TEXT_LINE_HEIGHT
|
offset_y += TEXT_LINE_HEIGHT
|
||||||
else:
|
elif span.width > 0:
|
||||||
# shift cursor
|
# only advance cursor if we actually rendered anything
|
||||||
offset_x += width
|
offset_x += span.width + SPACE
|
||||||
offset_x += SPACE
|
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
|
||||||
|
class DisplayMock:
|
||||||
|
"""Mock Display class that stores rendered text in an array.
|
||||||
|
|
||||||
|
Used to extract data for unit tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.screen_contents: List[str] = []
|
||||||
|
self.orig_display = ui.display
|
||||||
|
|
||||||
|
def __getattr__(self, key: str) -> Any:
|
||||||
|
return getattr(self.orig_display, key)
|
||||||
|
|
||||||
|
def __enter__(self) -> None:
|
||||||
|
ui.display = self # type: ignore
|
||||||
|
|
||||||
|
def __exit__(self, exc: Any, exc_type: Any, tb: Any) -> None:
|
||||||
|
ui.display = self.orig_display
|
||||||
|
|
||||||
|
def text(
|
||||||
|
self,
|
||||||
|
offset_x: int,
|
||||||
|
offset_y: int,
|
||||||
|
string: str,
|
||||||
|
font: int,
|
||||||
|
fg: int,
|
||||||
|
bg: int,
|
||||||
|
start: int = 0,
|
||||||
|
length: Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
|
if length is None:
|
||||||
|
length = len(string) - start
|
||||||
|
self.screen_contents.append(string[start : start + length])
|
||||||
|
|
||||||
|
|
||||||
class Text(ui.Component):
|
class Text(ui.Component):
|
||||||
@ -128,6 +356,11 @@ class Text(ui.Component):
|
|||||||
icon_color: int = ui.ORANGE_ICON,
|
icon_color: int = ui.ORANGE_ICON,
|
||||||
max_lines: int = TEXT_MAX_LINES,
|
max_lines: int = TEXT_MAX_LINES,
|
||||||
new_lines: bool = True,
|
new_lines: bool = True,
|
||||||
|
break_words: bool = False,
|
||||||
|
render_page_overflow: bool = True,
|
||||||
|
content_offset: int = 0,
|
||||||
|
char_offset: int = 0,
|
||||||
|
line_width: int = ui.WIDTH - TEXT_MARGIN_LEFT,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.header_text = header_text
|
self.header_text = header_text
|
||||||
@ -135,7 +368,12 @@ class Text(ui.Component):
|
|||||||
self.icon_color = icon_color
|
self.icon_color = icon_color
|
||||||
self.max_lines = max_lines
|
self.max_lines = max_lines
|
||||||
self.new_lines = new_lines
|
self.new_lines = new_lines
|
||||||
|
self.break_words = break_words
|
||||||
|
self.render_page_overflow = render_page_overflow
|
||||||
self.content: List[TextContent] = []
|
self.content: List[TextContent] = []
|
||||||
|
self.content_offset = content_offset
|
||||||
|
self.char_offset = char_offset
|
||||||
|
self.line_width = line_width
|
||||||
|
|
||||||
def normal(self, *content: TextContent) -> None:
|
def normal(self, *content: TextContent) -> None:
|
||||||
self.content.append(ui.NORMAL)
|
self.content.append(ui.NORMAL)
|
||||||
@ -164,14 +402,30 @@ class Text(ui.Component):
|
|||||||
ui.BG,
|
ui.BG,
|
||||||
self.icon_color,
|
self.icon_color,
|
||||||
)
|
)
|
||||||
render_text(self.content, self.new_lines, self.max_lines)
|
render_text(
|
||||||
|
self.content,
|
||||||
|
self.new_lines,
|
||||||
|
self.max_lines,
|
||||||
|
item_offset=self.content_offset,
|
||||||
|
char_offset=self.char_offset,
|
||||||
|
break_words=self.break_words,
|
||||||
|
line_width=self.line_width,
|
||||||
|
render_page_overflow=self.render_page_overflow,
|
||||||
|
)
|
||||||
self.repaint = False
|
self.repaint = False
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
|
|
||||||
def read_content(self) -> List[str]:
|
def read_content(self) -> List[str]:
|
||||||
lines = [w for w in self.content if isinstance(w, str)]
|
display_mock = DisplayMock()
|
||||||
return [self.header_text] + lines[: self.max_lines]
|
should_repaint = self.repaint
|
||||||
|
try:
|
||||||
|
with display_mock:
|
||||||
|
self.repaint = True
|
||||||
|
self.on_render()
|
||||||
|
finally:
|
||||||
|
self.repaint = should_repaint
|
||||||
|
return display_mock.screen_contents
|
||||||
|
|
||||||
|
|
||||||
LABEL_LEFT = const(0)
|
LABEL_LEFT = const(0)
|
||||||
|
Loading…
Reference in New Issue
Block a user