1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-05 13:26:57 +00:00

rust: Layout docs and improvements

This commit is contained in:
grdddj 2023-03-30 19:24:46 +02:00
parent 3c03645060
commit 9abecbdd9b
2 changed files with 54 additions and 19 deletions

View File

@ -29,7 +29,9 @@ pub enum PageMsg<T, U> {
}
pub trait Paginate {
/// How many pages of content are there in total?
fn page_count(&mut self) -> usize;
/// Navigate to the given page.
fn change_page(&mut self, active_page: usize);
}

View File

@ -54,9 +54,9 @@ pub struct TextLayout {
#[derive(Copy, Clone)]
pub struct TextStyle {
/// Text font ID. Can be overridden by `Op::Font`.
/// Text font ID.
pub text_font: Font,
/// Text color. Can be overridden by `Op::Color`.
/// Text color.
pub text_color: Color,
/// Background color.
pub background_color: Color,
@ -168,20 +168,28 @@ impl TextLayout {
self
}
/// Baseline `Point` where we are starting to draw the text.
pub fn initial_cursor(&self) -> Point {
let font = &self.style.text_font;
self.bounds.top_left()
+ Offset::y(font.text_max_height() - font.text_baseline() + self.padding_top)
}
/// Trying to fit the content on the current screen.
pub fn fit_text(&self, text: &str) -> LayoutFit {
self.layout_text(text, &mut self.initial_cursor(), &mut TextNoOp)
}
/// Draw as much text as possible on the current screen.
pub fn render_text(&self, text: &str) {
self.layout_text(text, &mut self.initial_cursor(), &mut TextRenderer);
}
/// Y coordinate of the bottom of the available space/bounds
pub fn bottom_y(&self) -> i16 {
(self.bounds.y1 - self.padding_bottom).max(self.bounds.y0)
}
pub fn layout_ops<'o>(
mut self,
ops: &mut dyn Iterator<Item = Op<'o>>,
@ -225,6 +233,9 @@ impl TextLayout {
}
}
/// Loop through the `text` and try to fit it on the current screen,
/// reporting events to `sink`, which may do something with them (e.g. draw
/// on screen).
pub fn layout_text(
&self,
text: &str,
@ -232,11 +243,10 @@ impl TextLayout {
sink: &mut dyn LayoutSink,
) -> LayoutFit {
let init_cursor = *cursor;
let bottom = (self.bounds.y1 - self.padding_bottom).max(self.bounds.y0);
let mut remaining_text = text;
// Check if bounding box is high enough for at least one line.
if cursor.y > bottom {
if cursor.y > self.bottom_y() {
sink.out_of_bounds();
return LayoutFit::OutOfBounds {
processed_chars: 0,
@ -251,6 +261,7 @@ impl TextLayout {
) && self.continues_from_prev_page
{
sink.prev_page_ellipsis(*cursor, self);
// Move the cursor to the right, always the same distance
cursor.x += self.style.prev_page_ellipsis_width();
}
@ -278,7 +289,12 @@ impl TextLayout {
};
// Report the span at the cursor position.
sink.text(*cursor, self, &remaining_text[..span.length]);
// Not doing it when the span length is 0, as that
// means we encountered a newline/line-break, which we do not draw.
// Line-breaks are reported later.
if span.length > 0 {
sink.text(*cursor, self, &remaining_text[..span.length]);
}
// Continue with the rest of the remaining_text.
remaining_text = &remaining_text[span.length + span.skip_next_chars..];
@ -294,7 +310,8 @@ impl TextLayout {
sink.hyphen(*cursor, self);
}
// Check the amount of vertical space we have left.
if cursor.y + span.advance.y > bottom {
if cursor.y + span.advance.y > self.bottom_y() {
// Not enough space on this page.
if !remaining_text.is_empty() {
// Append ellipsis to indicate more content is available, but only if we
// haven't already appended a hyphen.
@ -336,16 +353,13 @@ impl TextLayout {
}
}
/// Overall height of the content, including paddings.
fn layout_height(&self, init_cursor: Point, end_cursor: Point) -> i16 {
self.padding_top
+ self.style.text_font.text_height()
+ (end_cursor.y - init_cursor.y)
+ self.padding_bottom
}
fn bottom_y(&self) -> i16 {
(self.bounds.y1 - self.padding_bottom).max(self.bounds.y0)
}
}
impl Dimensions for TextLayout {
@ -358,6 +372,8 @@ impl Dimensions for TextLayout {
}
}
/// Whether we can fit content on the current screen.
/// Knows how many characters got processed and how high the content is.
pub enum LayoutFit {
/// Entire content fits. Vertical size is returned in `height`.
Fitting { processed_chars: usize, height: i16 },
@ -366,6 +382,7 @@ pub enum LayoutFit {
}
impl LayoutFit {
/// How high is the processed/fitted content.
pub fn height(&self) -> i16 {
match self {
LayoutFit::Fitting { height, .. } => *height,
@ -375,24 +392,36 @@ impl LayoutFit {
}
/// Visitor for text segment operations.
/// Defines responses for certain kind of events encountered
/// when processing the content.
pub trait LayoutSink {
/// Text should be processed.
fn text(&mut self, _cursor: Point, _layout: &TextLayout, _text: &str) {}
/// Hyphen at the end of line.
fn hyphen(&mut self, _cursor: Point, _layout: &TextLayout) {}
/// Ellipsis at the end of the page.
fn ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {}
/// Ellipsis at the beginning of the page.
fn prev_page_ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {}
/// Line break - a newline.
fn line_break(&mut self, _cursor: Point) {}
/// Content cannot fit on the screen.
fn out_of_bounds(&mut self) {}
}
/// `LayoutSink` without any functionality.
/// Used to consume events when counting pages
/// or navigating to a certain page number.
pub struct TextNoOp;
impl LayoutSink for TextNoOp {}
/// `LayoutSink` for rendering the content.
pub struct TextRenderer;
impl LayoutSink for TextRenderer {
fn text(&mut self, cursor: Point, layout: &TextLayout, text: &str) {
display::text(
display::text_left(
cursor,
text,
layout.style.text_font,
@ -402,7 +431,7 @@ impl LayoutSink for TextRenderer {
}
fn hyphen(&mut self, cursor: Point, layout: &TextLayout) {
display::text(
display::text_left(
cursor,
"-",
layout.style.text_font,
@ -413,14 +442,15 @@ impl LayoutSink for TextRenderer {
fn ellipsis(&mut self, cursor: Point, layout: &TextLayout) {
if let Some(icon) = layout.style.ellipsis_icon {
let bottom_left = cursor + Offset::new(layout.style.ellipsis_icon_margin, 1);
icon.draw(
cursor,
bottom_left,
BOTTOM_LEFT,
layout.style.ellipsis_color,
layout.style.background_color,
);
} else {
display::text(
display::text_left(
cursor,
ELLIPSIS,
layout.style.text_font,
@ -439,7 +469,7 @@ impl LayoutSink for TextRenderer {
layout.style.background_color,
);
} else {
display::text(
display::text_left(
cursor,
ELLIPSIS,
layout.style.text_font,
@ -456,6 +486,7 @@ pub mod trace {
use super::*;
/// `LayoutSink` for debugging purposes.
pub struct TraceSink<'a>(pub &'a mut dyn crate::trace::Tracer);
impl<'a> LayoutSink for TraceSink<'a> {
@ -468,11 +499,11 @@ pub mod trace {
}
fn ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {
self.0.string("...");
self.0.string(ELLIPSIS);
}
fn prev_page_ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {
self.0.string("...");
self.0.string(ELLIPSIS);
}
fn line_break(&mut self, _cursor: Point) {
@ -513,6 +544,8 @@ impl<'a> Op<'a> {
}
}
/// Carries info about the content that was processed
/// on the current line.
#[derive(Debug, PartialEq, Eq)]
pub struct Span {
/// How many characters from the input text this span is laying out.
@ -609,7 +642,7 @@ impl Span {
}
found_any_whitespace = true;
} else if span_width + char_width > max_width {
// Return the last breakpoint.
// Cannot fit on this line. Return the last breakpoint.
return line;
} else {
let have_space_for_break =
@ -631,7 +664,7 @@ impl Span {
span_width += char_width;
}
// The whole text is fitting.
// The whole text is fitting on the current line.
Self {
length: text.len(),
advance: Offset::x(span_width),