From b84322e4e94d87710eb76518e89ee66d1ca572a5 Mon Sep 17 00:00:00 2001 From: grdddj Date: Sun, 13 Nov 2022 08:05:09 +0100 Subject: [PATCH] WIP - scrollbar showing at most 5 dots of different sizes --- .../rust/src/ui/model_tr/component/page.rs | 25 +-- .../rust/src/ui/model_tr/component/pin.rs | 5 +- .../src/ui/model_tr/component/scrollbar.rs | 147 +++++++++++++++--- 3 files changed, 145 insertions(+), 32 deletions(-) diff --git a/core/embed/rust/src/ui/model_tr/component/page.rs b/core/embed/rust/src/ui/model_tr/component/page.rs index 06f4c9476f..869c3494a6 100644 --- a/core/embed/rust/src/ui/model_tr/component/page.rs +++ b/core/embed/rust/src/ui/model_tr/component/page.rs @@ -188,11 +188,11 @@ where fn place(&mut self, bounds: Rect) -> Rect { let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT); let content_area = content_area.inset(Insets::top(1)); - // Do not pad the button area nor the scrollbar, leave it to them + // Do not pad the button area nor the scrollbar, leave it to them. self.pad.place(content_area); self.content.place(content_area); // Need to be called here, only after content is placed - // and we can calculate the page count + // and we can calculate the page count. let page_count = self.content.inner_mut().page_count(); self.scrollbar.inner_mut().set_page_count(page_count); self.set_buttons_for_initial_page(page_count); @@ -201,18 +201,21 @@ where // Put it into its dedicated area when parent component already chose it, // otherwise place it into the right top of the content. if self.show_scrollbar { - let scrollbar_area = if let Some(scrollbar_area) = self.parent_scrollbar_area { + let max_scrollbar_area = if let Some(scrollbar_area) = self.parent_scrollbar_area { scrollbar_area } else { - Rect::from_top_right_and_size( - content_area.top_right(), - Offset::new( - -self.scrollbar.inner().overall_width(), - ScrollBar::MAX_DOT_SIZE, - ), - ) + content_area }; - self.scrollbar.place(scrollbar_area); + // Occupying as little space as possible (according to the number of pages), + // aligning to the right. + let min_scrollbar_area = Rect::from_top_right_and_size( + max_scrollbar_area.top_right(), + Offset::new( + self.scrollbar.inner().overall_width(), + ScrollBar::MAX_DOT_SIZE, + ), + ); + self.scrollbar.place(min_scrollbar_area); } self.buttons.place(button_area); diff --git a/core/embed/rust/src/ui/model_tr/component/pin.rs b/core/embed/rust/src/ui/model_tr/component/pin.rs index 22f4825347..a612f8ca8f 100644 --- a/core/embed/rust/src/ui/model_tr/component/pin.rs +++ b/core/embed/rust/src/ui/model_tr/component/pin.rs @@ -28,6 +28,7 @@ const CHOICE_LENGTH: usize = 13; const DELETE_INDEX: usize = 0; const SHOW_INDEX: usize = 1; const PROMPT_INDEX: usize = 2; +const NUMBER_START_INDEX: usize = 3; const CHOICES: [&str; CHOICE_LENGTH] = [ "DELETE", "SHOW", @@ -100,7 +101,7 @@ impl PinEntry { Self { // Starting at the digit 0 choice_page: ChoicePage::new(choices) - .with_initial_page_counter(PROMPT_INDEX as u8 + 1) + .with_initial_page_counter(NUMBER_START_INDEX as u8) .with_carousel(true), pin_dots: Child::new(ChangingTextLine::center_mono(String::new())), show_real_pin: false, @@ -188,7 +189,7 @@ impl Component for PinEntry { self.update_pin_dots(ctx); // Choosing any random digit to be shown next let new_page_counter = random::uniform_between( - PROMPT_INDEX as u32 + 1, + NUMBER_START_INDEX as u32, (CHOICE_LENGTH - 1) as u32, ); self.choice_page diff --git a/core/embed/rust/src/ui/model_tr/component/scrollbar.rs b/core/embed/rust/src/ui/model_tr/component/scrollbar.rs index 62b8e148b6..54eacf5968 100644 --- a/core/embed/rust/src/ui/model_tr/component/scrollbar.rs +++ b/core/embed/rust/src/ui/model_tr/component/scrollbar.rs @@ -5,6 +5,8 @@ use crate::ui::{ model_tr::theme, }; +use heapless::Vec; + /// Scrollbar to be painted horizontally at the top right of the screen. pub struct ScrollBar { area: Rect, @@ -13,15 +15,25 @@ pub struct ScrollBar { pub active_page: usize, } +/// Carrying the appearance of the scrollbar dot. +#[derive(Debug)] +enum DotType { + BigFull, + Big, + Middle, + Small, +} + +/// How many dots at most will there be +const MAX_DOTS: usize = 5; + impl ScrollBar { - /// How many dots at most will there be - pub const MAX_DOTS: i16 = 5; /// Maximum size (width/height) of a dot pub const MAX_DOT_SIZE: i16 = 5; /// Distance between two dots pub const DOTS_DISTANCE: i16 = 2; pub const DOTS_INTERVAL: i16 = Self::MAX_DOT_SIZE + Self::DOTS_DISTANCE; - pub const MAX_WIDTH: i16 = Self::DOTS_INTERVAL * Self::MAX_DOTS - Self::DOTS_DISTANCE; + pub const MAX_WIDTH: i16 = Self::DOTS_INTERVAL * MAX_DOTS as i16 - Self::DOTS_DISTANCE; pub fn new(page_count: usize) -> Self { Self { @@ -37,6 +49,7 @@ impl ScrollBar { Self::new(0) } + /// The width the scrollbar will really occupy. pub fn overall_width(&self) -> i16 { Self::DOTS_INTERVAL * self.page_count as i16 - Self::DOTS_DISTANCE } @@ -68,33 +81,129 @@ impl ScrollBar { /// Create a (seemingly circular) dot given its top left point. /// Make it full when it is active, otherwise paint just the perimeter and /// leave center empty. - fn paint_dot(&self, active: bool, top_right: Point) { + fn paint_dot(&self, dot_type: &DotType, top_right: Point) { let full_square = Rect::from_top_right_and_size(top_right, Offset::uniform(Self::MAX_DOT_SIZE)); - // FG - painting the full square - display::rect_fill(full_square, theme::FG); + match dot_type { + DotType::BigFull | DotType::Big => { + // FG - painting the full square + display::rect_fill(full_square, theme::FG); - // BG - erase four corners - for p in full_square.corner_points().iter() { - display::paint_point(p, theme::BG); - } + // BG - erase four corners + for p in full_square.corner_points().iter() { + display::paint_point(p, theme::BG); + } - // BG - erasing the middle when not active - if !active { - display::rect_fill(full_square.shrink(1), theme::BG) + // BG - erasing the middle when not full + if matches!(dot_type, DotType::Big) { + display::rect_fill(full_square.shrink(1), theme::BG) + } + } + DotType::Middle => { + let middle_square = full_square.shrink(1); + + // FG - painting the middle square + display::rect_fill(middle_square, theme::FG); + + // BG - erase four corners + for p in middle_square.corner_points().iter() { + display::paint_point(p, theme::BG); + } + + // BG - erasing the middle + display::rect_fill(middle_square.shrink(1), theme::BG) + } + DotType::Small => { + // FG - painting the small square + display::rect_fill(full_square.shrink(2), theme::FG) + } } } - /// Drawing the dots horizontally and aligning to the right + /// Get a sequence of dots to be drawn, with specifying their appearance. + /// Painting only big dots in case of 2 and 3 pages, + /// three big and 1 middle in case of 4 pages, + /// and three big, one middle and one small in case of 5 and more pages. + fn get_drawable_dots(&self) -> Vec { + let mut dots = Vec::new(); + + match self.page_count { + small_num if small_num < 4 => { + for i in 0..self.page_count { + if i == self.active_page { + unwrap!(dots.push(DotType::BigFull)); + } else { + unwrap!(dots.push(DotType::Big)); + } + } + } + 4 => { + match self.active_page { + 0 => unwrap!(dots.push(DotType::BigFull)), + 1 => unwrap!(dots.push(DotType::Big)), + _ => unwrap!(dots.push(DotType::Middle)), + }; + match self.active_page { + 1 => unwrap!(dots.push(DotType::BigFull)), + _ => unwrap!(dots.push(DotType::Big)), + }; + match self.active_page { + 2 => unwrap!(dots.push(DotType::BigFull)), + _ => unwrap!(dots.push(DotType::Big)), + }; + match self.active_page { + 3 => unwrap!(dots.push(DotType::BigFull)), + 2 => unwrap!(dots.push(DotType::Big)), + _ => unwrap!(dots.push(DotType::Middle)), + }; + } + _ => { + let full_dot_index = match self.active_page { + 0 => 0, + 1 => 1, + last if last == self.page_count - 1 => 4, + last_but_one if last_but_one == self.page_count - 2 => 3, + _ => 2, + }; + match full_dot_index { + 0 => unwrap!(dots.push(DotType::BigFull)), + 1 => unwrap!(dots.push(DotType::Big)), + 2 => unwrap!(dots.push(DotType::Middle)), + _ => unwrap!(dots.push(DotType::Small)), + }; + match full_dot_index { + 0 => unwrap!(dots.push(DotType::Big)), + 1 => unwrap!(dots.push(DotType::BigFull)), + 2 => unwrap!(dots.push(DotType::Big)), + _ => unwrap!(dots.push(DotType::Middle)), + }; + match full_dot_index { + 2 => unwrap!(dots.push(DotType::BigFull)), + _ => unwrap!(dots.push(DotType::Big)), + }; + match full_dot_index { + 0 | 1 => unwrap!(dots.push(DotType::Middle)), + 3 => unwrap!(dots.push(DotType::BigFull)), + _ => unwrap!(dots.push(DotType::Big)), + }; + match full_dot_index { + 0 | 1 => unwrap!(dots.push(DotType::Small)), + 2 => unwrap!(dots.push(DotType::Middle)), + 3 => unwrap!(dots.push(DotType::Big)), + _ => unwrap!(dots.push(DotType::BigFull)), + }; + } + } + dots + } + + /// Drawing the dots horizontally and aligning to the right. fn paint_horizontal(&mut self) { let mut top_right = self.area.top_right(); - // TODO: implement smaller dots - two more sizes - // TODO: implement showing at most MAX_DIGITS - for i in (0..self.page_count).rev() { - self.paint_dot(i == self.active_page, top_right); + for dot in self.get_drawable_dots().iter().rev() { + self.paint_dot(dot, top_right); top_right.x -= Self::DOTS_INTERVAL; - top_right.print(); } } }