diff --git a/core/embed/rust/src/ui/component/label.rs b/core/embed/rust/src/ui/component/label.rs index 72dd112d4..63a52a8f5 100644 --- a/core/embed/rust/src/ui/component/label.rs +++ b/core/embed/rust/src/ui/component/label.rs @@ -23,7 +23,7 @@ where T: Deref, { pub fn new(origin: Point, align: Alignment, text: T, style: LabelStyle) -> Self { - let width = display::text_width(&text, style.font); + let width = style.font.text_width(&text); let height = style.font.line_height(); let area = match align { // `origin` is the top-left point. diff --git a/core/embed/rust/src/ui/component/pad.rs b/core/embed/rust/src/ui/component/pad.rs index e4bea5c02..670e1dd78 100644 --- a/core/embed/rust/src/ui/component/pad.rs +++ b/core/embed/rust/src/ui/component/pad.rs @@ -26,7 +26,7 @@ impl Pad { if self.clear { self.clear = false; - display::rect(self.area, self.color); + display::rect_fill(self.area, self.color); } } } diff --git a/core/embed/rust/src/ui/component/text/layout.rs b/core/embed/rust/src/ui/component/text/layout.rs index 199b0e298..6ba971d70 100644 --- a/core/embed/rust/src/ui/component/text/layout.rs +++ b/core/embed/rust/src/ui/component/text/layout.rs @@ -397,7 +397,7 @@ impl Span { // break. let mut line = Self { length: 0, - advance: Offset::new(0, text_font.line_height()), + advance: Offset::y(text_font.line_height()), insert_hyphen_before_line_break: false, skip_next_chars: 0, }; @@ -447,7 +447,7 @@ impl Span { // The whole text is fitting. Self { length: text.len(), - advance: Offset::new(span_width, 0), + advance: Offset::x(span_width), insert_hyphen_before_line_break: false, skip_next_chars: 0, } diff --git a/core/embed/rust/src/ui/component/text/paragraphs.rs b/core/embed/rust/src/ui/component/text/paragraphs.rs index 95d76910f..6fe1f2d6a 100644 --- a/core/embed/rust/src/ui/component/text/paragraphs.rs +++ b/core/embed/rust/src/ui/component/text/paragraphs.rs @@ -189,7 +189,7 @@ where match fit { LayoutFit::Fitting { size, .. } => { // Text fits, update the bounding box. - let (used, free) = area.hsplit(size.y); + let (used, free) = area.split_top(size.y); paragraph.set_area(used); // Continue with next paragraph in remaining space. area = free; diff --git a/core/embed/rust/src/ui/display.rs b/core/embed/rust/src/ui/display.rs index 37dc8ad2a..a4201ae9c 100644 --- a/core/embed/rust/src/ui/display.rs +++ b/core/embed/rust/src/ui/display.rs @@ -49,11 +49,19 @@ pub fn fade_backlight(target: i32) { } } -pub fn rect(r: Rect, fg_color: Color) { +pub fn rect_fill(r: Rect, fg_color: Color) { display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into()); } -pub fn rounded_rect(r: Rect, fg_color: Color, bg_color: Color, radius: u8) { +pub fn rect_stroke(r: Rect, fg_color: Color) { + display::bar(r.x0, r.y0, r.width(), 1, fg_color.into()); + display::bar(r.x0, r.y0 + r.height() - 1, r.width(), 1, fg_color.into()); + display::bar(r.x0, r.y0, 1, r.height(), fg_color.into()); + display::bar(r.x0 + r.width() - 1, r.y0, 1, r.height(), fg_color.into()); +} + +pub fn rect_fill_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u8) { + assert!([2, 4, 8, 16].iter().any(|allowed| radius == *allowed)); display::bar_radius( r.x0, r.y0, @@ -65,6 +73,22 @@ pub fn rounded_rect(r: Rect, fg_color: Color, bg_color: Color, radius: u8) { ); } +/// NOTE: Cannot start at odd x-coordinate. In this case icon is shifted 1px +/// left. +pub fn icon_top_left(top_left: Point, data: &[u8], fg_color: Color, bg_color: Color) { + let toif_info = display::toif_info(data).unwrap(); + assert!(toif_info.grayscale); + display::icon( + top_left.x, + top_left.y, + toif_info.width.into(), + toif_info.height.into(), + &data[12..], // Skip TOIF header. + fg_color.into(), + bg_color.into(), + ); +} + pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) { let toif_info = display::toif_info(data).unwrap(); assert!(toif_info.grayscale); @@ -85,13 +109,13 @@ pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) { } // Used on T1 only. -pub fn rounded_rect1(r: Rect, fg_color: Color, bg_color: Color) { +pub fn rect_fill_rounded1(r: Rect, fg_color: Color, bg_color: Color) { display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into()); let corners = [ r.top_left(), - r.top_right() + Offset::new(-1, 0), - r.bottom_right() + Offset::new(-1, -1), - r.bottom_left() + Offset::new(0, -1), + r.top_right() - Offset::x(1), + r.bottom_right() - Offset::uniform(1), + r.bottom_left() - Offset::y(1), ]; for p in corners.iter() { display::bar(p.x, p.y, 1, 1, bg_color.into()); @@ -156,7 +180,7 @@ pub fn text(baseline: Point, text: &[u8], font: Font, fg_color: Color, bg_color: } pub fn text_center(baseline: Point, text: &[u8], font: Font, fg_color: Color, bg_color: Color) { - let w = text_width(text, font); + let w = font.text_width(text); display::text( baseline.x - w / 2, baseline.y, @@ -167,10 +191,6 @@ pub fn text_center(baseline: Point, text: &[u8], font: Font, fg_color: Color, bg ); } -pub fn text_width(text: &[u8], font: Font) -> i32 { - display::text_width(text, font.0) -} - #[derive(Copy, Clone, PartialEq, Eq)] pub struct Font(i32); @@ -180,7 +200,7 @@ impl Font { } pub fn text_width(self, text: &[u8]) -> i32 { - text_width(text, self) + display::text_width(text, self.0) } pub fn text_height(self) -> i32 { diff --git a/core/embed/rust/src/ui/geometry.rs b/core/embed/rust/src/ui/geometry.rs index 5837248a0..94fc01b18 100644 --- a/core/embed/rust/src/ui/geometry.rs +++ b/core/embed/rust/src/ui/geometry.rs @@ -22,6 +22,14 @@ impl Offset { Self::new(0, 0) } + pub const fn x(x: i32) -> Self { + Self::new(x, 0) + } + + pub const fn y(y: i32) -> Self { + Self::new(0, y) + } + pub fn on_axis(axis: Axis, a: i32) -> Self { match axis { Axis::Horizontal => Self::new(a, 0), @@ -29,6 +37,13 @@ impl Offset { } } + pub fn axis(&self, axis: Axis) -> i32 { + match axis { + Axis::Horizontal => self.x, + Axis::Vertical => self.y, + } + } + pub fn abs(self) -> Self { Self::new(self.x.abs(), self.y.abs()) } @@ -165,16 +180,20 @@ impl Rect { self.top_left().center(self.bottom_right()) } + pub fn bottom_center(&self) -> Point { + self.bottom_left().center(self.bottom_right()) + } + pub fn contains(&self, point: Point) -> bool { point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1 } - pub fn inset(&self, uniform: i32) -> Self { + pub fn inset(&self, insets: Insets) -> Self { Self { - x0: self.x0 + uniform, - y0: self.y0 + uniform, - x1: self.x1 - uniform, - y1: self.y1 - uniform, + x0: self.x0 + insets.left, + y0: self.y0 + insets.top, + x1: self.x1 - insets.right, + y1: self.y1 - insets.bottom, } } @@ -196,53 +215,80 @@ impl Rect { } } - pub fn hsplit(self, height: i32) -> (Self, Self) { - let height = if height.is_negative() { - self.height() + height - } else { - height - }; + pub fn split_top(self, height: i32) -> (Self, Self) { + let height = height.clamp(0, self.height()); let top = Self { - x0: self.x0, - y0: self.y0, - x1: self.x1, y1: self.y0 + height, + ..self }; - let bottom = Self { - x0: self.x0, y0: self.y0 + height, - x1: self.x1, - y1: self.y1, + ..self }; - (top, bottom) } - pub fn vsplit(self, width: i32) -> (Self, Self) { - let width = if width.is_negative() { - self.width() + width - } else { - width - }; + pub fn split_bottom(self, height: i32) -> (Self, Self) { + self.split_top(self.height() - height) + } + + pub fn split_left(self, width: i32) -> (Self, Self) { + let width = width.clamp(0, self.width()); let left = Self { - x0: self.x0, - y0: self.y0, x1: self.x0 + width, - y1: self.y1, + ..self }; - let right = Self { x0: self.x0 + width, - y0: self.y0, - x1: self.x1, - y1: self.y1, + ..self }; - (left, right) } + + pub fn split_right(self, width: i32) -> (Self, Self) { + self.split_left(self.width() - width) + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Insets { + pub top: i32, + pub right: i32, + pub bottom: i32, + pub left: i32, +} + +impl Insets { + pub const fn new(top: i32, right: i32, bottom: i32, left: i32) -> Self { + Self { + top, + right, + bottom, + left, + } + } + + pub const fn uniform(d: i32) -> Self { + Self::new(d, d, d, d) + } + + pub const fn top(d: i32) -> Self { + Self::new(d, 0, 0, 0) + } + + pub const fn right(d: i32) -> Self { + Self::new(0, d, 0, 0) + } + + pub const fn bottom(d: i32) -> Self { + Self::new(0, 0, d, 0) + } + + pub const fn left(d: i32) -> Self { + Self::new(0, 0, 0, d) + } } #[derive(Copy, Clone, PartialEq, Eq)] @@ -295,17 +341,50 @@ impl Grid { } } + pub fn with_spacing(mut self, spacing: i32) -> Self { + self.spacing = spacing; + self + } + pub fn row_col(&self, row: usize, col: usize) -> Rect { - let cell_width = self.area.width() / self.cols as i32; - let cell_height = self.area.height() / self.rows as i32; - let x = col as i32 * cell_width; - let y = row as i32 * cell_height; - Rect { - x0: self.area.x0 + x, - y0: self.area.y0 + y, - x1: self.area.x0 + x + (cell_width - self.spacing), - y1: self.area.y0 + y + (cell_height - self.spacing), + let ncols = self.cols as i32; + let nrows = self.rows as i32; + let col = (col as i32).min(ncols - 1); + let row = (row as i32).min(nrows - 1); + + // Total number of horizontal pixels used for spacing. + let spacing_width = self.spacing * (ncols - 1); + let spacing_height = self.spacing * (nrows - 1); + + // Divide what is left by number of cells to obtain width of each cell. + let cell_width = (self.area.width() - spacing_width) / ncols; + let cell_height = (self.area.height() - spacing_height) / nrows; + + // Not every area can be fully covered by equal-sized cells and spaces, there + // might be serveral pixels left unused. We'll distribute them by 1px to + // the leftmost cells. + let leftover_width = (self.area.width() - spacing_width) % ncols; + let leftover_height = (self.area.height() - spacing_height) % nrows; + + let mut top_left = self.area.top_left() + + Offset::new( + col * (cell_width + self.spacing), + row * (cell_height + self.spacing), + ); + // Some previous cells were 1px wider. + top_left.x += leftover_width.min(col); + top_left.y += leftover_height.min(row); + + let mut size = Offset::new(cell_width, cell_height); + // This cell might be 1px wider. + if col < leftover_width { + size.x += 1 } + if row < leftover_height { + size.y += 1 + } + + Rect::from_top_left_and_size(top_left, size) } pub fn cell(&self, index: usize) -> Rect { @@ -357,45 +436,67 @@ impl LinearLayout { self } - /// Arranges all `items` by parameters configured in `self` into `area`. - /// Does not change the size of the items (only the position), but it needs - /// to iterate (and ask for the size) twice. - pub fn arrange(&self, area: Rect, items: &mut [impl Dimensions]) { - let item_sum: i32 = items - .iter_mut() - .map(|i| { - let size = i.get_size(); - self.axis.main(size.x, size.y) - }) - .sum(); - let spacing_count = items.len().saturating_sub(1); + fn compute_spacing(&self, area: Rect, count: usize, size_sum: i32) -> (i32, i32) { + let spacing_count = count.saturating_sub(1); let spacing_sum = spacing_count as i32 * self.spacing; - let naive_size = item_sum + spacing_sum; - let available_space = match self.axis { - Axis::Horizontal => area.width(), - Axis::Vertical => area.height(), - }; + let naive_size = size_sum + spacing_sum; + let available_space = area.size().axis(self.axis); // scale down spacing to fit everything into area let (total_size, spacing) = if naive_size > available_space { - let scaled_space = (available_space - item_sum) / spacing_count as i32; + let scaled_space = (available_space - size_sum) / spacing_count as i32; // forbid negative spacing (available_space, scaled_space.max(0)) } else { (naive_size, self.spacing) }; - let mut cursor = match self.align { + let init_cursor = match self.align { Alignment::Start => 0, Alignment::Center => available_space / 2 - total_size / 2, Alignment::End => available_space - total_size, }; + (init_cursor, spacing) + } + + /// Arranges all `items` by parameters configured in `self` into `area`. + /// Does not change the size of the items (only the position), but it needs + /// to iterate (and ask for the size) twice. + pub fn arrange(&self, area: Rect, items: &mut [impl Dimensions]) { + let item_sum: i32 = items.iter_mut().map(|i| i.get_size().axis(self.axis)).sum(); + let (mut cursor, spacing) = self.compute_spacing(area, items.len(), item_sum); + for item in items { let top_left = area.top_left() + Offset::on_axis(self.axis, cursor); let size = item.get_size(); item.set_area(Rect::from_top_left_and_size(top_left, size)); - cursor += self.axis.main(size.x, size.y); + cursor += size.axis(self.axis); + cursor += spacing; + } + } + + /// Arranges number of items of the same size into `area`. The `sink` + /// closure is called `count` times with top left point of each item as + /// argument. Items are centered along the cross axis. + pub fn arrange_uniform( + &self, + area: Rect, + count: usize, + size: Offset, + sink: &mut dyn FnMut(Point), + ) { + let item_size = size.axis(self.axis); + let (mut cursor, spacing) = self.compute_spacing(area, count, (count as i32) * item_size); + let cross_coord = + area.size().axis(self.axis.cross()) / 2 - size.axis(self.axis.cross()) / 2; + + for _ in 0..count { + let top_left = area.top_left() + + Offset::on_axis(self.axis, cursor) + + Offset::on_axis(self.axis.cross(), cross_coord); + sink(top_left); + cursor += item_size; cursor += spacing; } } diff --git a/core/embed/rust/src/ui/model_t1/component/button.rs b/core/embed/rust/src/ui/model_t1/component/button.rs index f1f081930..76fc36246 100644 --- a/core/embed/rust/src/ui/model_t1/component/button.rs +++ b/core/embed/rust/src/ui/model_t1/component/button.rs @@ -94,13 +94,13 @@ impl> Button { ) -> (Rect, Point) { let border_width = if styles.normal.border_horiz { 2 } else { 0 }; let content_width = match content { - ButtonContent::Text(text) => display::text_width(text.as_ref(), styles.normal.font) - 1, + ButtonContent::Text(text) => styles.normal.font.text_width(text.as_ref()) - 1, ButtonContent::Icon(_icon) => todo!(), }; let button_width = content_width + 2 * border_width; let area = match pos { - ButtonPos::Left => area.vsplit(button_width).0, - ButtonPos::Right => area.vsplit(-button_width).1, + ButtonPos::Left => area.split_left(button_width).0, + ButtonPos::Right => area.split_right(button_width).1, }; let start_of_baseline = area.bottom_left() + Offset::new(border_width, -2); @@ -135,9 +135,9 @@ impl> Component for Button { ButtonContent::Text(text) => { let background_color = style.text_color.neg(); if style.border_horiz { - display::rounded_rect1(self.area, background_color, theme::BG); + display::rect_fill_rounded1(self.area, background_color, theme::BG); } else { - display::rect(self.area, background_color) + display::rect_fill(self.area, background_color) } display::text( diff --git a/core/embed/rust/src/ui/model_t1/component/dialog.rs b/core/embed/rust/src/ui/model_t1/component/dialog.rs index 27f709e1b..ad265f3c1 100644 --- a/core/embed/rust/src/ui/model_t1/component/dialog.rs +++ b/core/embed/rust/src/ui/model_t1/component/dialog.rs @@ -39,7 +39,7 @@ impl> Dialog { fn areas(area: Rect) -> (Rect, Rect) { let button_height = theme::FONT_BOLD.line_height() + 2; - let (content_area, button_area) = area.hsplit(-button_height); + let (content_area, button_area) = area.split_bottom(button_height); (content_area, button_area) } } diff --git a/core/embed/rust/src/ui/model_t1/component/frame.rs b/core/embed/rust/src/ui/model_t1/component/frame.rs index 0635af434..7573cf952 100644 --- a/core/embed/rust/src/ui/model_t1/component/frame.rs +++ b/core/embed/rust/src/ui/model_t1/component/frame.rs @@ -25,8 +25,8 @@ impl> Frame { const HEADER_SPACE: i32 = 4; let header_height = theme::FONT_BOLD.line_height(); - let (header_area, content_area) = area.hsplit(header_height); - let (_space, content_area) = content_area.hsplit(HEADER_SPACE); + let (header_area, content_area) = area.split_top(header_height); + let (_space, content_area) = content_area.split_top(HEADER_SPACE); (header_area, content_area) } @@ -41,7 +41,7 @@ impl> Component for Frame { fn paint(&mut self) { display::text( - self.area.bottom_left() + Offset::new(0, -2), + self.area.bottom_left() - Offset::y(2), self.title.as_ref(), theme::FONT_BOLD, theme::FG, diff --git a/core/embed/rust/src/ui/model_t1/component/page.rs b/core/embed/rust/src/ui/model_t1/component/page.rs index 2f06163db..c22a15491 100644 --- a/core/embed/rust/src/ui/model_t1/component/page.rs +++ b/core/embed/rust/src/ui/model_t1/component/page.rs @@ -1,7 +1,7 @@ use crate::ui::{ component::{Component, ComponentExt, Event, EventCtx, Never, Pad, PageMsg, Paginate}, display::{self, Color}, - geometry::{Offset, Point, Rect}, + geometry::{Insets, Offset, Point, Rect}, }; use super::{theme, Button, ButtonMsg, ButtonPos}; @@ -63,9 +63,9 @@ where fn areas(area: Rect) -> (Rect, Rect, Rect) { let button_height = theme::FONT_BOLD.line_height() + 2; - let (content_area, button_area) = area.hsplit(-button_height); - let (content_area, scrollbar_area) = content_area.vsplit(-ScrollBar::WIDTH); - let (content_area, _) = content_area.hsplit(-1); + let (content_area, button_area) = area.split_bottom(button_height); + let (content_area, scrollbar_area) = content_area.split_right(ScrollBar::WIDTH); + let (content_area, _) = content_area.split_bottom(1); (content_area, scrollbar_area, button_area) } @@ -182,8 +182,8 @@ impl ScrollBar { fn paint_dot(&self, active: bool, top_left: Point) { let sides = [ - Rect::from_top_left_and_size(top_left + Offset::new(1, 0), Offset::new(2, 1)), - Rect::from_top_left_and_size(top_left + Offset::new(0, 1), Offset::new(1, 2)), + Rect::from_top_left_and_size(top_left + Offset::x(1), Offset::new(2, 1)), + Rect::from_top_left_and_size(top_left + Offset::y(1), Offset::new(1, 2)), Rect::from_top_left_and_size( top_left + Offset::new(1, Self::DOT_SIZE.y - 1), Offset::new(2, 1), @@ -194,11 +194,11 @@ impl ScrollBar { ), ]; for side in sides { - display::rect(side, theme::FG) + display::rect_fill(side, theme::FG) } if active { - display::rect( - Rect::from_top_left_and_size(top_left, Self::DOT_SIZE).inset(1), + display::rect_fill( + Rect::from_top_left_and_size(top_left, Self::DOT_SIZE).inset(Insets::uniform(1)), theme::FG, ) } diff --git a/core/embed/rust/src/ui/model_tt/component/button.rs b/core/embed/rust/src/ui/model_tt/component/button.rs index 4eecac57f..88b50c416 100644 --- a/core/embed/rust/src/ui/model_tt/component/button.rs +++ b/core/embed/rust/src/ui/model_tt/component/button.rs @@ -2,7 +2,7 @@ use super::{event::TouchEvent, theme}; use crate::ui::{ component::{Component, Event, EventCtx, Map}, display::{self, Color, Font}, - geometry::{Grid, Offset, Rect}, + geometry::{Grid, Insets, Offset, Rect}, }; pub enum ButtonMsg { @@ -160,14 +160,14 @@ where if style.border_width > 0 { // Paint the border and a smaller background on top of it. - display::rounded_rect( + display::rect_fill_rounded( self.area, style.border_color, style.background_color, style.border_radius, ); - display::rounded_rect( - self.area.inset(style.border_width), + display::rect_fill_rounded( + self.area.inset(Insets::uniform(style.border_width)), style.button_color, style.border_color, style.border_radius, @@ -175,7 +175,7 @@ where } else { // We do not need to draw an explicit border in this case, just a // bigger background. - display::rounded_rect( + display::rect_fill_rounded( self.area, style.button_color, style.background_color, diff --git a/core/embed/rust/src/ui/model_tt/component/frame.rs b/core/embed/rust/src/ui/model_tt/component/frame.rs index 98b113499..f025e2a27 100644 --- a/core/embed/rust/src/ui/model_tt/component/frame.rs +++ b/core/embed/rust/src/ui/model_tt/component/frame.rs @@ -29,9 +29,9 @@ where const HEADER_SPACE: i32 = 14; let header_height = theme::FONT_BOLD.line_height() - theme::CONTENT_BORDER; - let (header_area, content_area) = area.hsplit(header_height); - let (_space, header_area) = header_area.vsplit(theme::CONTENT_BORDER); - let (_space, content_area) = content_area.hsplit(HEADER_SPACE); + let (header_area, content_area) = area.split_top(header_height); + let (_space, header_area) = header_area.split_left(theme::CONTENT_BORDER); + let (_space, content_area) = content_area.split_top(HEADER_SPACE); (header_area, content_area) } diff --git a/core/embed/rust/src/ui/model_tt/component/page.rs b/core/embed/rust/src/ui/model_tt/component/page.rs index 1eda1bd01..d7f8387fc 100644 --- a/core/embed/rust/src/ui/model_tt/component/page.rs +++ b/core/embed/rust/src/ui/model_tt/component/page.rs @@ -3,7 +3,7 @@ use crate::ui::{ base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, Never, Pad, Paginate, }, display::{self, Color}, - geometry::{Dimensions, Offset, Point, Rect}, + geometry::{Dimensions, LinearLayout, Offset, Rect}, }; use super::{theme, Button, Swipe, SwipeDirection}; @@ -82,7 +82,7 @@ where fn paint_hint(&mut self) { display::text_center( - Point::new(self.pad.area.center().x, self.pad.area.bottom_right().y - 3), + self.pad.area.bottom_center() - Offset::y(3), b"SWIPE TO CONTINUE", theme::FONT_BOLD, // FIXME: Figma has this as 14px but bold is 16px theme::GREY_LIGHT, @@ -180,8 +180,11 @@ pub struct ScrollBar { } impl ScrollBar { - const DOT_INTERVAL: i32 = 12; - const ARROW_SPACE: i32 = 23; + const DOT_SIZE: i32 = 6; + /// Edge to edge. + const DOT_INTERVAL: i32 = 6; + /// Edge of last dot to center of arrow icon. + const ARROW_SPACE: i32 = 26; const ICON_ACTIVE: &'static [u8] = include_res!("model_tt/res/scroll-active.toif"); const ICON_INACTIVE: &'static [u8] = include_res!("model_tt/res/scroll-inactive.toif"); @@ -229,40 +232,42 @@ impl Component for ScrollBar { } fn paint(&mut self) { - let count = self.page_count as i32; - let interval = { - let available_height = self.area.height(); - let naive_height = count * Self::DOT_INTERVAL; - if naive_height > available_height { - available_height / count + let layout = LinearLayout::vertical() + .align_at_center() + .with_spacing(Self::DOT_INTERVAL); + + let mut i = 0; + let mut top = None; + let mut display_icon = |top_left| { + let icon = if i == self.active_page { + Self::ICON_ACTIVE } else { - Self::DOT_INTERVAL - } + Self::ICON_INACTIVE + }; + display::icon_top_left(top_left, icon, theme::FG, theme::BG); + i += 1; + top.get_or_insert(top_left.x); }; - let mut dot = Point::new( - self.area.center().x, - self.area.center().y - (count / 2) * interval, + + layout.arrange_uniform( + self.area, + self.page_count, + Offset::new(Self::DOT_SIZE, Self::DOT_SIZE), + &mut display_icon, ); + + let arrow_distance = self.area.center().x - top.unwrap_or(0) + Self::ARROW_SPACE; if self.has_previous_page() { display::icon( - dot - Offset::new(0, Self::ARROW_SPACE), + self.area.center() - Offset::y(arrow_distance), Self::ICON_UP, theme::FG, theme::BG, ); } - for i in 0..self.page_count { - let icon = if i == self.active_page { - Self::ICON_ACTIVE - } else { - Self::ICON_INACTIVE - }; - display::icon(dot, icon, theme::FG, theme::BG); - dot.y += interval; - } if self.has_next_page() { display::icon( - dot + Offset::new(0, Self::ARROW_SPACE - interval), + self.area.center() + Offset::y(arrow_distance), Self::ICON_DOWN, theme::FG, theme::BG, @@ -284,13 +289,14 @@ impl PageLayout { const SCROLLBAR_SPACE: i32 = 10; pub fn new(area: Rect) -> Self { - let (content, buttons) = area.hsplit(-Button::HEIGHT); - let (content, _space) = content.hsplit(-Self::BUTTON_SPACE); - let (buttons, _space) = buttons.vsplit(-theme::CONTENT_BORDER); - let (_space, content) = content.vsplit(theme::CONTENT_BORDER); - let (content_single_page, _space) = content.vsplit(-theme::CONTENT_BORDER); - let (content, scrollbar) = content.vsplit(-(Self::SCROLLBAR_SPACE + Self::SCROLLBAR_WIDTH)); - let (_space, scrollbar) = scrollbar.vsplit(Self::SCROLLBAR_SPACE); + let (content, buttons) = area.split_bottom(Button::<&str>::HEIGHT); + let (content, _space) = content.split_bottom(Self::BUTTON_SPACE); + let (buttons, _space) = buttons.split_right(theme::CONTENT_BORDER); + let (_space, content) = content.split_left(theme::CONTENT_BORDER); + let (content_single_page, _space) = content.split_right(theme::CONTENT_BORDER); + let (content, scrollbar) = + content.split_right(Self::SCROLLBAR_SPACE + Self::SCROLLBAR_WIDTH); + let (_space, scrollbar) = scrollbar.split_left(Self::SCROLLBAR_SPACE); Self { content_single_page, diff --git a/core/embed/rust/src/ui/model_tt/component/pin.rs b/core/embed/rust/src/ui/model_tt/component/pin.rs index de4b31d2d..a7de9738f 100644 --- a/core/embed/rust/src/ui/model_tt/component/pin.rs +++ b/core/embed/rust/src/ui/model_tt/component/pin.rs @@ -222,7 +222,7 @@ impl Component for PinDots { fn paint(&mut self) { // Clear the area with the background color. - display::rect(self.area, self.style.background_color); + display::rect_fill(self.area, self.style.background_color); // Draw a dot for each PIN digit. for i in 0..self.digit_count { @@ -231,7 +231,7 @@ impl Component for PinDots { y: self.area.center().y, }; let size = Offset::new(Self::DOT, Self::DOT); - display::rounded_rect( + display::rect_fill_rounded( Rect::from_top_left_and_size(pos, size), self.style.text_color, self.style.background_color, diff --git a/core/embed/rust/src/ui/model_tt/theme.rs b/core/embed/rust/src/ui/model_tt/theme.rs index 867c88a7b..fbb3e60da 100644 --- a/core/embed/rust/src/ui/model_tt/theme.rs +++ b/core/embed/rust/src/ui/model_tt/theme.rs @@ -1,7 +1,7 @@ use crate::ui::{ component::{label::LabelStyle, text::layout::DefaultTextTheme}, display::{self, Color, Font}, - geometry::Rect, + geometry::{Insets, Rect}, }; use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet}; @@ -164,9 +164,5 @@ pub const CONTENT_BORDER: i32 = 5; /// | 14 | /// +----------+ pub fn borders() -> Rect { - let (_left_border, area) = display::screen().vsplit(10); - let (area, _right_area) = area.vsplit(-5); - let (_top_border, area) = area.hsplit(13); - let (area, _bottom_border) = area.hsplit(-14); - area + display::screen().inset(Insets::new(13, 5, 14, 10)) }