mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-24 15:38:22 +00:00
WIP - simplify the flow_helpers, consider moving more things into layout.rs
This commit is contained in:
parent
96d0a7b2e7
commit
e8c8c7d053
@ -68,6 +68,9 @@ pub struct TextStyle {
|
||||
pub line_breaking: LineBreaking,
|
||||
/// Specifies what to do at the end of the page.
|
||||
pub page_breaking: PageBreaking,
|
||||
|
||||
/// Specifies how to align text on the line.
|
||||
pub line_alignment: Alignment,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
@ -86,6 +89,7 @@ impl TextStyle {
|
||||
ellipsis_color,
|
||||
line_breaking: LineBreaking::BreakAtWhitespace,
|
||||
page_breaking: PageBreaking::CutAndInsertEllipsis,
|
||||
line_alignment: Alignment::Start,
|
||||
ellipsis_icon: None,
|
||||
prev_page_icon: None,
|
||||
}
|
||||
@ -261,7 +265,7 @@ impl TextLayout {
|
||||
}
|
||||
|
||||
/// Overall height of the content, including paddings.
|
||||
fn layout_height(&self, init_cursor: Point, end_cursor: Point) -> i16 {
|
||||
pub 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)
|
||||
@ -413,7 +417,7 @@ pub struct Span {
|
||||
}
|
||||
|
||||
impl Span {
|
||||
fn fit_horizontally(
|
||||
pub fn fit_horizontally(
|
||||
text: &str,
|
||||
max_width: i16,
|
||||
text_font: impl GlyphMetrics,
|
||||
|
@ -1,7 +1,10 @@
|
||||
use crate::{
|
||||
micropython::buffer::StrBuffer,
|
||||
ui::{
|
||||
component::Paginate,
|
||||
component::{
|
||||
text::{layout::LayoutFit, TextStyle},
|
||||
Paginate, TextLayout,
|
||||
},
|
||||
display::{Font, Icon, IconAndName},
|
||||
geometry::{Alignment, Offset, Point, Rect},
|
||||
model_tr::theme,
|
||||
@ -12,10 +15,7 @@ use crate::{
|
||||
use heapless::Vec;
|
||||
|
||||
use super::{
|
||||
flow_pages_poc_helpers::{
|
||||
LayoutFit, LayoutSink, Op, QrCodeInfo, TextLayout, TextNoOp, TextRenderer, TextStyle,
|
||||
ToDisplay,
|
||||
},
|
||||
flow_pages_helpers::{LayoutSink, Op, QrCodeInfo, TextNoOp, TextRenderer, ToDisplay},
|
||||
ButtonActions, ButtonDetails, ButtonLayout,
|
||||
};
|
||||
|
||||
@ -340,7 +340,7 @@ impl<const M: usize> Paginate for Page<M> {
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
pub mod trace {
|
||||
use crate::ui::model_tr::component::flow_pages_poc_helpers::TraceSink;
|
||||
use crate::ui::model_tr::component::flow_pages_helpers::TraceSink;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -1,12 +1,27 @@
|
||||
//! Mostly copy-pasted stuff from ui/component/text,
|
||||
//! but with small modifications.
|
||||
//! Mostly extending TextLayout from ui/component/text/layout.rs
|
||||
//! (support for more Ops like icon drawing or arbitrary offsets)
|
||||
//! - unfortunately means there is quite a lot of duplication at the
|
||||
//! benefit of not polluting the original code things not used there
|
||||
//! (icons, QR codes)
|
||||
//!
|
||||
//! TODO: CONSIDERATION:
|
||||
//! A) maintain this little dirty state
|
||||
//! - biggest duplications are in `layout_text`, which needed to be
|
||||
//! completely copied and renamed to `layout_text_new` just to use the new
|
||||
//! `Sink` here, which supports the icon and QR code
|
||||
//! B) move all the new code into ui/component/text/layout.rs
|
||||
//! - would mean moving there QrCodeInfo, Sink::QrCode, Sink::Icon, etc.
|
||||
//! My preference is B, but we need to agree on that
|
||||
|
||||
use crate::{
|
||||
micropython::buffer::StrBuffer,
|
||||
ui::{
|
||||
component::{
|
||||
text::layout::{LayoutFit, Span},
|
||||
PageBreaking, TextLayout,
|
||||
},
|
||||
display::{self, Color, Font, Icon},
|
||||
geometry::{Alignment, Offset, Point, Rect},
|
||||
geometry::{Alignment, Offset, Point},
|
||||
},
|
||||
};
|
||||
|
||||
@ -76,134 +91,7 @@ pub enum Op {
|
||||
NextPage,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum LineBreaking {
|
||||
/// Break line only at whitespace, if possible. If we don't find any
|
||||
/// whitespace, break words.
|
||||
BreakAtWhitespace,
|
||||
/// Break words, adding a hyphen before the line-break. Does not use any
|
||||
/// smart algorithm, just char-by-char.
|
||||
BreakWordsAndInsertHyphen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PageBreaking {
|
||||
/// Stop after hitting the bottom-right edge of the bounds.
|
||||
Cut,
|
||||
/// Before stopping at the bottom-right edge, insert ellipsis to signify
|
||||
/// more content is available, but only if no hyphen has been inserted yet.
|
||||
CutAndInsertEllipsis,
|
||||
}
|
||||
|
||||
/// Visual instructions for laying out a formatted block of text.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TextLayout {
|
||||
/// Bounding box restricting the layout dimensions.
|
||||
pub bounds: Rect,
|
||||
|
||||
/// Additional space before beginning of text, can be negative to shift text
|
||||
/// upwards.
|
||||
pub padding_top: i16,
|
||||
/// Additional space between end of text and bottom of bounding box, can be
|
||||
/// negative.
|
||||
pub padding_bottom: i16,
|
||||
|
||||
/// Fonts, colors, line/page breaking behavior.
|
||||
pub style: TextStyle,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TextStyle {
|
||||
/// Text font ID. Can be overridden by `Op::Font`.
|
||||
pub text_font: Font,
|
||||
/// Text color. Can be overridden by `Op::Color`.
|
||||
pub text_color: Color,
|
||||
/// Background color.
|
||||
pub background_color: Color,
|
||||
|
||||
/// Foreground color used for drawing the hyphen.
|
||||
pub hyphen_color: Color,
|
||||
/// Foreground color used for drawing the ellipsis.
|
||||
pub ellipsis_color: Color,
|
||||
|
||||
/// Optional icon shown as ellipsis.
|
||||
pub ellipsis_icon: Option<&'static [u8]>,
|
||||
/// Optional icon to signal content continues from previous page.
|
||||
pub prev_page_icon: Option<&'static [u8]>,
|
||||
|
||||
/// Specifies which line-breaking strategy to use.
|
||||
pub line_breaking: LineBreaking,
|
||||
/// Specifies what to do at the end of the page.
|
||||
pub page_breaking: PageBreaking,
|
||||
|
||||
/// Specifies how to align text on the line.
|
||||
pub line_alignment: Alignment,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub const fn new(
|
||||
text_font: Font,
|
||||
text_color: Color,
|
||||
background_color: Color,
|
||||
hyphen_color: Color,
|
||||
ellipsis_color: Color,
|
||||
) -> Self {
|
||||
TextStyle {
|
||||
text_font,
|
||||
text_color,
|
||||
background_color,
|
||||
hyphen_color,
|
||||
ellipsis_color,
|
||||
line_breaking: LineBreaking::BreakAtWhitespace,
|
||||
page_breaking: PageBreaking::CutAndInsertEllipsis,
|
||||
line_alignment: Alignment::Start,
|
||||
ellipsis_icon: None,
|
||||
prev_page_icon: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adding optional icon shown instead of "..." ellipsis.
|
||||
pub const fn with_ellipsis_icon(mut self, icon: &'static [u8]) -> Self {
|
||||
self.ellipsis_icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adding optional icon signalling content continues from previous page.
|
||||
pub const fn with_prev_page_icon(mut self, icon: &'static [u8]) -> Self {
|
||||
self.prev_page_icon = Some(icon);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl TextLayout {
|
||||
/// Create a new text layout, with empty size and default text parameters
|
||||
/// filled from `T`.
|
||||
pub fn new(style: TextStyle) -> Self {
|
||||
Self {
|
||||
bounds: Rect::zero(),
|
||||
padding_top: 0,
|
||||
padding_bottom: 0,
|
||||
style,
|
||||
}
|
||||
}
|
||||
|
||||
/// Baseline `Point` where we are starting to draw the text.
|
||||
pub fn initial_cursor(&self) -> Point {
|
||||
// TODO: do NOT add the text_font height here, as it can be changed - each page
|
||||
// can have its own font and the Y offset would be wrong.
|
||||
self.bounds.top_left() + Offset::y(self.style.text_font.text_height() + self.padding_top)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// X coordinate of the right of the available space/bounds
|
||||
pub fn right_x(&self) -> i16 {
|
||||
self.bounds.x1
|
||||
}
|
||||
|
||||
/// Perform some operations defined on `Op` for a list of those `Op`s
|
||||
/// - e.g. changing the color, changing the font or rendering the text.
|
||||
pub fn layout_ops<const M: usize>(
|
||||
@ -316,7 +204,7 @@ impl TextLayout {
|
||||
// (start > 0),
|
||||
// in which case we could/should start the text with
|
||||
// an arrow icon (opposite to ellipsis)
|
||||
self.layout_text(to_really_display, cursor, sink)
|
||||
self.layout_text_new(to_really_display, cursor, sink)
|
||||
} else if let Op::Icon(icon) = op {
|
||||
self.layout_icon(icon, cursor, sink)
|
||||
} else {
|
||||
@ -354,7 +242,7 @@ 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(
|
||||
pub fn layout_text_new(
|
||||
&self,
|
||||
text: &str,
|
||||
cursor: &mut Point,
|
||||
@ -379,13 +267,20 @@ impl TextLayout {
|
||||
// instead of the hyphen (have it `ellipsis_length`)
|
||||
|
||||
while !remaining_text.is_empty() {
|
||||
let remaining_width = self.bounds.x1 - cursor.x;
|
||||
let span = Span::fit_horizontally(
|
||||
remaining_text,
|
||||
self.bounds.x1 - cursor.x,
|
||||
remaining_width,
|
||||
self.style.text_font,
|
||||
self.style.line_breaking,
|
||||
);
|
||||
|
||||
cursor.x += match self.align {
|
||||
Alignment::Start => 0,
|
||||
Alignment::Center => (remaining_width - span.advance.x) / 2,
|
||||
Alignment::End => remaining_width - span.advance.x,
|
||||
};
|
||||
|
||||
// Report the span at the cursor position.
|
||||
// Not doing it when the span length is 0, as that
|
||||
// means we encountered a newline/line-break, which we do not draw.
|
||||
@ -411,6 +306,7 @@ impl TextLayout {
|
||||
}
|
||||
// Check the amount of vertical space we have left.
|
||||
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. Also not doing it if the last
|
||||
@ -470,7 +366,7 @@ impl TextLayout {
|
||||
|
||||
// Icon is too wide to fit on current line.
|
||||
// Trying to accommodate it on the next line, when it exists on this page.
|
||||
if cursor.x + icon.width() > self.right_x() {
|
||||
if cursor.x + icon.width() > self.bounds.x1 {
|
||||
cursor.x = self.bounds.x0;
|
||||
cursor.y += self.style.text_font.line_height();
|
||||
if cursor.y > self.bottom_y() {
|
||||
@ -497,33 +393,6 @@ impl TextLayout {
|
||||
pub fn layout_qr_code(&self, qr_code_info: QrCodeInfo, sink: &mut dyn LayoutSink) {
|
||||
sink.qrcode(qr_code_info);
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 },
|
||||
/// Content fits partially or not at all.
|
||||
OutOfBounds { processed_chars: usize, height: i16 },
|
||||
}
|
||||
|
||||
impl LayoutFit {
|
||||
/// How high is the processed/fitted content.
|
||||
pub fn height(&self) -> i16 {
|
||||
match self {
|
||||
LayoutFit::Fitting { height, .. } => *height,
|
||||
LayoutFit::OutOfBounds { height, .. } => *height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor for text segment operations.
|
||||
@ -680,123 +549,3 @@ impl<'a> LayoutSink for TraceSink<'a> {
|
||||
self.0.string("\n");
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GlyphMetrics {
|
||||
fn char_width(&self, ch: char) -> i16;
|
||||
fn line_height(&self) -> i16;
|
||||
}
|
||||
|
||||
impl GlyphMetrics for Font {
|
||||
fn char_width(&self, ch: char) -> i16 {
|
||||
Font::char_width(*self, ch)
|
||||
}
|
||||
|
||||
fn line_height(&self) -> i16 {
|
||||
Font::line_height(*self)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: rename to `LineSpan`?
|
||||
/// 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.
|
||||
pub length: usize,
|
||||
/// How many chars from the input text should we skip before fitting the
|
||||
/// next span?
|
||||
pub skip_next_chars: usize,
|
||||
/// By how much to offset the cursor after this span. If the vertical offset
|
||||
/// is bigger than zero, it means we are breaking the line.
|
||||
pub advance: Offset,
|
||||
/// If we are breaking the line, should we insert a hyphen right after this
|
||||
/// span to indicate a word-break?
|
||||
pub insert_hyphen_before_line_break: bool,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
fn fit_horizontally(
|
||||
text: &str,
|
||||
max_width: i16,
|
||||
text_font: impl GlyphMetrics,
|
||||
breaking: LineBreaking,
|
||||
) -> Self {
|
||||
const ASCII_LF: char = '\n';
|
||||
const ASCII_CR: char = '\r';
|
||||
const ASCII_SPACE: char = ' ';
|
||||
const ASCII_HYPHEN: char = '-';
|
||||
|
||||
fn is_whitespace(ch: char) -> bool {
|
||||
ch == ASCII_SPACE || ch == ASCII_LF || ch == ASCII_CR
|
||||
}
|
||||
|
||||
let hyphen_width = text_font.char_width(ASCII_HYPHEN);
|
||||
|
||||
// The span we return in case the line has to break. We mutate it in the
|
||||
// possible break points, and its initial value is returned in case no text
|
||||
// at all is fitting the constraints: zero length, zero width, full line
|
||||
// break.
|
||||
let mut line = Self {
|
||||
length: 0,
|
||||
advance: Offset::y(text_font.line_height()),
|
||||
insert_hyphen_before_line_break: false,
|
||||
skip_next_chars: 0,
|
||||
};
|
||||
|
||||
let mut span_width = 0;
|
||||
let mut found_any_whitespace = false;
|
||||
|
||||
let mut char_indices_iter = text.char_indices().peekable();
|
||||
// Iterating manually because we need a reference to the iterator inside the
|
||||
// loop.
|
||||
while let Some((i, ch)) = char_indices_iter.next() {
|
||||
let char_width = text_font.char_width(ch);
|
||||
|
||||
// Consider if we could be breaking the line at this position.
|
||||
if is_whitespace(ch) {
|
||||
// Break before the whitespace, without hyphen.
|
||||
line.length = i;
|
||||
line.advance.x = span_width;
|
||||
line.insert_hyphen_before_line_break = false;
|
||||
line.skip_next_chars = 1;
|
||||
if ch == ASCII_CR {
|
||||
// We'll be breaking the line, but advancing the cursor only by a half of the
|
||||
// regular line height.
|
||||
line.advance.y = text_font.line_height() / 2;
|
||||
}
|
||||
if ch == ASCII_LF || ch == ASCII_CR {
|
||||
// End of line, break immediately.
|
||||
return line;
|
||||
}
|
||||
found_any_whitespace = true;
|
||||
} else if span_width + char_width > max_width {
|
||||
// Cannot fit on this line. Return the last breakpoint.
|
||||
return line;
|
||||
} else {
|
||||
let have_space_for_break = span_width + char_width + hyphen_width <= max_width;
|
||||
let can_break_word = matches!(breaking, LineBreaking::BreakWordsAndInsertHyphen)
|
||||
|| !found_any_whitespace;
|
||||
if have_space_for_break && can_break_word {
|
||||
// Break after this character, append hyphen.
|
||||
line.length = match char_indices_iter.peek() {
|
||||
Some((idx, _)) => *idx,
|
||||
None => text.len(),
|
||||
};
|
||||
line.advance.x = span_width + char_width;
|
||||
line.insert_hyphen_before_line_break = true;
|
||||
line.skip_next_chars = 0;
|
||||
}
|
||||
}
|
||||
|
||||
span_width += char_width;
|
||||
}
|
||||
|
||||
// The whole text is fitting on the current line.
|
||||
Self {
|
||||
length: text.len(),
|
||||
advance: Offset::x(span_width),
|
||||
insert_hyphen_before_line_break: false,
|
||||
skip_next_chars: 0,
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ mod changing_text;
|
||||
mod common;
|
||||
mod flow;
|
||||
mod flow_pages;
|
||||
mod flow_pages_poc_helpers;
|
||||
mod flow_pages_helpers;
|
||||
mod frame;
|
||||
mod hold_to_confirm;
|
||||
mod homescreen;
|
||||
|
Loading…
Reference in New Issue
Block a user