mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-05-31 21:28:46 +00:00
refactor(core/rust/ui): paragraphs breaking
Also fix corner case. [no changelog]
This commit is contained in:
parent
b5da6dc911
commit
10650af1fa
@ -230,14 +230,16 @@ impl TextLayout {
|
|||||||
pub fn measure_ops_height(self, ops: &mut dyn Iterator<Item = Op>) -> i32 {
|
pub fn measure_ops_height(self, ops: &mut dyn Iterator<Item = Op>) -> i32 {
|
||||||
match self.layout_ops(ops, &mut self.initial_cursor(), &mut TextNoOp) {
|
match self.layout_ops(ops, &mut self.initial_cursor(), &mut TextNoOp) {
|
||||||
LayoutFit::Fitting { size, .. } => size.y,
|
LayoutFit::Fitting { size, .. } => size.y,
|
||||||
LayoutFit::OutOfBounds { .. } => self.bounds.height(),
|
LayoutFit::OutOfBounds { processed_chars: 0 } => 0,
|
||||||
|
_ => self.bounds.height(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn measure_text_height(self, text: &[u8]) -> i32 {
|
pub fn measure_text_height(self, text: &[u8]) -> i32 {
|
||||||
match self.layout_text(text, &mut self.initial_cursor(), &mut TextNoOp) {
|
match self.layout_text(text, &mut self.initial_cursor(), &mut TextNoOp) {
|
||||||
LayoutFit::Fitting { size, .. } => size.y,
|
LayoutFit::Fitting { size, .. } => size.y,
|
||||||
LayoutFit::OutOfBounds { .. } => self.bounds.height(),
|
LayoutFit::OutOfBounds { processed_chars: 0 } => 0,
|
||||||
|
_ => self.bounds.height(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use heapless::Vec;
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{Component, Event, EventCtx, Never, Paginate},
|
component::{Component, Event, EventCtx, Never, Paginate},
|
||||||
display::Font,
|
display::Font,
|
||||||
geometry::{Dimensions, LinearLayout, Offset, Rect},
|
geometry::{Dimensions, Insets, LinearLayout, Offset, Rect},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::layout::{DefaultTextTheme, LayoutFit, TextLayout, TextNoOp, TextRenderer};
|
use super::layout::{DefaultTextTheme, LayoutFit, TextLayout, TextNoOp, TextRenderer};
|
||||||
@ -15,8 +15,8 @@ pub struct Paragraphs<T> {
|
|||||||
area: Rect,
|
area: Rect,
|
||||||
list: Vec<Paragraph<T>, MAX_PARAGRAPHS>,
|
list: Vec<Paragraph<T>, MAX_PARAGRAPHS>,
|
||||||
layout: LinearLayout,
|
layout: LinearLayout,
|
||||||
para_offset: usize,
|
offset: PageOffset,
|
||||||
char_offset: usize,
|
visible: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Paragraphs<T>
|
impl<T> Paragraphs<T>
|
||||||
@ -30,8 +30,8 @@ where
|
|||||||
layout: LinearLayout::vertical()
|
layout: LinearLayout::vertical()
|
||||||
.align_at_center()
|
.align_at_center()
|
||||||
.with_spacing(DEFAULT_SPACING),
|
.with_spacing(DEFAULT_SPACING),
|
||||||
para_offset: 0,
|
offset: PageOffset::default(),
|
||||||
char_offset: 0,
|
visible: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,11 +63,38 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn break_pages<'a>(&'a mut self) -> PageBreakIterator<'a, T> {
|
/// Update bounding boxes of paragraphs on the current page. First determine
|
||||||
|
/// the number of visible paragraphs and their sizes. These are then
|
||||||
|
/// arranged according to the layout.
|
||||||
|
fn change_offset(&mut self, offset: PageOffset) {
|
||||||
|
self.offset = offset;
|
||||||
|
self.visible = 0;
|
||||||
|
let mut char_offset = offset.chr;
|
||||||
|
let mut remaining_area = self.area;
|
||||||
|
|
||||||
|
for paragraph in self.list.iter_mut().skip(offset.par) {
|
||||||
|
paragraph.set_area(remaining_area);
|
||||||
|
let height = paragraph
|
||||||
|
.layout
|
||||||
|
.measure_text_height(¶graph.content.as_ref()[char_offset..]);
|
||||||
|
if height == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let (used, free) = remaining_area.split_top(height);
|
||||||
|
paragraph.set_area(used);
|
||||||
|
remaining_area = free;
|
||||||
|
self.visible += 1;
|
||||||
|
char_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let visible_paragraphs = &mut self.list[offset.par..offset.par + self.visible];
|
||||||
|
self.layout.arrange(self.area, visible_paragraphs);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn break_pages<'a>(&'a self) -> PageBreakIterator<'a, T> {
|
||||||
PageBreakIterator {
|
PageBreakIterator {
|
||||||
paragraphs: self,
|
paragraphs: self,
|
||||||
para_offset: 0,
|
current: None,
|
||||||
char_offset: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,16 +110,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
let mut char_offset = self.char_offset;
|
let mut char_offset = self.offset.chr;
|
||||||
for paragraph in self.list.iter().skip(self.para_offset) {
|
for paragraph in self.list.iter().skip(self.offset.par).take(self.visible) {
|
||||||
let fit = paragraph.layout.layout_text(
|
paragraph.layout.layout_text(
|
||||||
¶graph.content.as_ref()[char_offset..],
|
¶graph.content.as_ref()[char_offset..],
|
||||||
&mut paragraph.layout.initial_cursor(),
|
&mut paragraph.layout.initial_cursor(),
|
||||||
&mut TextRenderer,
|
&mut TextRenderer,
|
||||||
);
|
);
|
||||||
if matches!(fit, LayoutFit::OutOfBounds { .. }) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
char_offset = 0;
|
char_offset = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,6 +144,13 @@ where
|
|||||||
}
|
}
|
||||||
t.close();
|
t.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &dyn Fn(Rect)) {
|
||||||
|
sink(self.area);
|
||||||
|
for paragraph in self.list.iter().skip(self.offset.par).take(self.visible) {
|
||||||
|
paragraph.bounds(sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Paragraph<T> {
|
pub struct Paragraph<T> {
|
||||||
@ -149,75 +180,81 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PageBreakIterator<'a, T> {
|
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
||||||
paragraphs: &'a mut Paragraphs<T>,
|
struct PageOffset {
|
||||||
para_offset: usize,
|
/// Index of paragraph.
|
||||||
char_offset: usize,
|
par: usize,
|
||||||
|
|
||||||
|
/// Index of character in the paragraph.
|
||||||
|
chr: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Yields indices to beginnings of successive pages. As a side effect it
|
struct PageBreakIterator<'a, T> {
|
||||||
/// updates the bounding box of each paragraph on the page. Because a paragraph
|
/// Reference to paragraph vector.
|
||||||
/// can be rendered on multiple pages, such bounding boxes are only valid for
|
paragraphs: &'a Paragraphs<T>,
|
||||||
/// paragraphs processed in the last call to `next`.
|
|
||||||
///
|
/// Current offset, or `None` before first `next()` call.
|
||||||
/// The boxes are simply stacked below each other and may be further arranged
|
current: Option<PageOffset>,
|
||||||
/// before drawing.
|
}
|
||||||
|
|
||||||
|
/// Yields indices to beginnings of successive pages. First value is always
|
||||||
|
/// `PageOffset { 0, 0 }` even if the paragraph vector is empty.
|
||||||
impl<'a, T> Iterator for PageBreakIterator<'a, T>
|
impl<'a, T> Iterator for PageBreakIterator<'a, T>
|
||||||
where
|
where
|
||||||
T: AsRef<[u8]>,
|
T: AsRef<[u8]>,
|
||||||
{
|
{
|
||||||
/// Paragraph index, character index, number of paragraphs shown.
|
/// `PageOffset` denotes the first paragraph that is rendered and a
|
||||||
type Item = (usize, usize, usize);
|
/// character offset in that paragraph.
|
||||||
|
type Item = PageOffset;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.para_offset >= self.paragraphs.list.len() {
|
let first = self.current.is_none();
|
||||||
return None;
|
let current = self.current.get_or_insert_with(PageOffset::default);
|
||||||
|
if first {
|
||||||
|
return self.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_para_offset = self.para_offset;
|
let mut remaining_area = self.paragraphs.area;
|
||||||
let old_char_offset = self.char_offset;
|
let mut progress = false;
|
||||||
let mut area = self.paragraphs.area;
|
|
||||||
|
|
||||||
for paragraph in self.paragraphs.list.iter_mut().skip(self.para_offset) {
|
for paragraph in self.paragraphs.list.iter().skip(current.par) {
|
||||||
loop {
|
loop {
|
||||||
paragraph.set_area(area);
|
let mut temp_layout = paragraph.layout;
|
||||||
let fit = paragraph.layout.layout_text(
|
temp_layout.bounds = remaining_area;
|
||||||
¶graph.content.as_ref()[self.char_offset..],
|
|
||||||
&mut paragraph.layout.initial_cursor(),
|
let fit = temp_layout.layout_text(
|
||||||
|
¶graph.content.as_ref()[current.chr..],
|
||||||
|
&mut temp_layout.initial_cursor(),
|
||||||
&mut TextNoOp,
|
&mut TextNoOp,
|
||||||
);
|
);
|
||||||
match fit {
|
match fit {
|
||||||
LayoutFit::Fitting { size, .. } => {
|
LayoutFit::Fitting { size, .. } => {
|
||||||
// Text fits, update the bounding box.
|
// Text fits, update remaining area.
|
||||||
let (used, free) = area.split_top(size.y);
|
remaining_area = remaining_area.inset(Insets::top(size.y));
|
||||||
paragraph.set_area(used);
|
|
||||||
// Continue with next paragraph in remaining space.
|
// Continue with start of next paragraph.
|
||||||
area = free;
|
current.par += 1;
|
||||||
self.char_offset = 0;
|
current.chr = 0;
|
||||||
self.para_offset += 1;
|
progress = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
LayoutFit::OutOfBounds { processed_chars } => {
|
LayoutFit::OutOfBounds { processed_chars } => {
|
||||||
// Text does not fit, assume whatever fits takes the entire remaining area.
|
// Text does not fit, assume whatever fits takes the entire remaining area.
|
||||||
self.char_offset += processed_chars;
|
current.chr += processed_chars;
|
||||||
let visible = if processed_chars > 0 {
|
if processed_chars == 0 && !progress {
|
||||||
self.para_offset - old_para_offset + 1
|
// Nothing fits yet page is empty: terminate iterator to avoid looping
|
||||||
} else {
|
// forever.
|
||||||
self.para_offset - old_para_offset
|
return None;
|
||||||
};
|
}
|
||||||
// Return pointer to start of page.
|
// Return current offset.
|
||||||
return Some((old_para_offset, old_char_offset, visible));
|
return self.current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last page.
|
// Last page.
|
||||||
Some((
|
None
|
||||||
old_para_offset,
|
|
||||||
old_char_offset,
|
|
||||||
self.para_offset - old_para_offset,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,29 +264,19 @@ where
|
|||||||
{
|
{
|
||||||
fn page_count(&mut self) -> usize {
|
fn page_count(&mut self) -> usize {
|
||||||
// There's always at least one page.
|
// There's always at least one page.
|
||||||
let page_count = self.break_pages().count().max(1);
|
self.break_pages().count().max(1)
|
||||||
|
|
||||||
// Reset to first page.
|
|
||||||
self.change_page(0);
|
|
||||||
|
|
||||||
page_count
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_page(&mut self, to_page: usize) {
|
fn change_page(&mut self, to_page: usize) {
|
||||||
if let Some((para_offset, char_offset, para_visible)) =
|
if let Some(offset) = self.break_pages().skip(to_page).next() {
|
||||||
self.break_pages().skip(to_page).next()
|
self.change_offset(offset)
|
||||||
{
|
|
||||||
// Set offsets used by `paint`.
|
|
||||||
self.para_offset = para_offset;
|
|
||||||
self.char_offset = char_offset;
|
|
||||||
|
|
||||||
// Arrange visible paragraphs.
|
|
||||||
let visible = &mut self.list[para_offset..para_offset + para_visible];
|
|
||||||
self.layout.arrange(self.area, visible);
|
|
||||||
} else {
|
} else {
|
||||||
// Should not happen, set index past last paragraph to render empty page.
|
// Should not happen, set index past last paragraph to render empty page.
|
||||||
self.para_offset = self.list.len();
|
self.offset = PageOffset {
|
||||||
self.char_offset = 0;
|
par: self.list.len(),
|
||||||
|
chr: 0,
|
||||||
|
};
|
||||||
|
self.visible = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ where
|
|||||||
// Reduce area to make space for scrollbar if it doesn't fit.
|
// Reduce area to make space for scrollbar if it doesn't fit.
|
||||||
content.set_area(layout.content);
|
content.set_area(layout.content);
|
||||||
}
|
}
|
||||||
|
content.change_page(0);
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user