1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-26 00:18:15 +00:00

WIP - scrollbar showing at most 5 dots of different sizes

This commit is contained in:
grdddj 2022-11-13 08:05:09 +01:00
parent ed4395b386
commit b84322e4e9
3 changed files with 145 additions and 32 deletions

View File

@ -188,11 +188,11 @@ where
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT); let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT);
let content_area = content_area.inset(Insets::top(1)); 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.pad.place(content_area);
self.content.place(content_area); self.content.place(content_area);
// Need to be called here, only after content is placed // 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(); let page_count = self.content.inner_mut().page_count();
self.scrollbar.inner_mut().set_page_count(page_count); self.scrollbar.inner_mut().set_page_count(page_count);
self.set_buttons_for_initial_page(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, // Put it into its dedicated area when parent component already chose it,
// otherwise place it into the right top of the content. // otherwise place it into the right top of the content.
if self.show_scrollbar { 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 scrollbar_area
} else { } else {
Rect::from_top_right_and_size( content_area
content_area.top_right(), };
// 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( Offset::new(
-self.scrollbar.inner().overall_width(), self.scrollbar.inner().overall_width(),
ScrollBar::MAX_DOT_SIZE, ScrollBar::MAX_DOT_SIZE,
), ),
) );
}; self.scrollbar.place(min_scrollbar_area);
self.scrollbar.place(scrollbar_area);
} }
self.buttons.place(button_area); self.buttons.place(button_area);

View File

@ -28,6 +28,7 @@ const CHOICE_LENGTH: usize = 13;
const DELETE_INDEX: usize = 0; const DELETE_INDEX: usize = 0;
const SHOW_INDEX: usize = 1; const SHOW_INDEX: usize = 1;
const PROMPT_INDEX: usize = 2; const PROMPT_INDEX: usize = 2;
const NUMBER_START_INDEX: usize = 3;
const CHOICES: [&str; CHOICE_LENGTH] = [ const CHOICES: [&str; CHOICE_LENGTH] = [
"DELETE", "DELETE",
"SHOW", "SHOW",
@ -100,7 +101,7 @@ impl PinEntry {
Self { Self {
// Starting at the digit 0 // Starting at the digit 0
choice_page: ChoicePage::new(choices) 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), .with_carousel(true),
pin_dots: Child::new(ChangingTextLine::center_mono(String::new())), pin_dots: Child::new(ChangingTextLine::center_mono(String::new())),
show_real_pin: false, show_real_pin: false,
@ -188,7 +189,7 @@ impl Component for PinEntry {
self.update_pin_dots(ctx); self.update_pin_dots(ctx);
// Choosing any random digit to be shown next // Choosing any random digit to be shown next
let new_page_counter = random::uniform_between( let new_page_counter = random::uniform_between(
PROMPT_INDEX as u32 + 1, NUMBER_START_INDEX as u32,
(CHOICE_LENGTH - 1) as u32, (CHOICE_LENGTH - 1) as u32,
); );
self.choice_page self.choice_page

View File

@ -5,6 +5,8 @@ use crate::ui::{
model_tr::theme, model_tr::theme,
}; };
use heapless::Vec;
/// Scrollbar to be painted horizontally at the top right of the screen. /// Scrollbar to be painted horizontally at the top right of the screen.
pub struct ScrollBar { pub struct ScrollBar {
area: Rect, area: Rect,
@ -13,15 +15,25 @@ pub struct ScrollBar {
pub active_page: usize, 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 { impl ScrollBar {
/// How many dots at most will there be
pub const MAX_DOTS: i16 = 5;
/// Maximum size (width/height) of a dot /// Maximum size (width/height) of a dot
pub const MAX_DOT_SIZE: i16 = 5; pub const MAX_DOT_SIZE: i16 = 5;
/// Distance between two dots /// Distance between two dots
pub const DOTS_DISTANCE: i16 = 2; pub const DOTS_DISTANCE: i16 = 2;
pub const DOTS_INTERVAL: i16 = Self::MAX_DOT_SIZE + Self::DOTS_DISTANCE; 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 { pub fn new(page_count: usize) -> Self {
Self { Self {
@ -37,6 +49,7 @@ impl ScrollBar {
Self::new(0) Self::new(0)
} }
/// The width the scrollbar will really occupy.
pub fn overall_width(&self) -> i16 { pub fn overall_width(&self) -> i16 {
Self::DOTS_INTERVAL * self.page_count as i16 - Self::DOTS_DISTANCE Self::DOTS_INTERVAL * self.page_count as i16 - Self::DOTS_DISTANCE
} }
@ -68,10 +81,12 @@ impl ScrollBar {
/// Create a (seemingly circular) dot given its top left point. /// Create a (seemingly circular) dot given its top left point.
/// Make it full when it is active, otherwise paint just the perimeter and /// Make it full when it is active, otherwise paint just the perimeter and
/// leave center empty. /// 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 = let full_square =
Rect::from_top_right_and_size(top_right, Offset::uniform(Self::MAX_DOT_SIZE)); Rect::from_top_right_and_size(top_right, Offset::uniform(Self::MAX_DOT_SIZE));
match dot_type {
DotType::BigFull | DotType::Big => {
// FG - painting the full square // FG - painting the full square
display::rect_fill(full_square, theme::FG); display::rect_fill(full_square, theme::FG);
@ -80,21 +95,115 @@ impl ScrollBar {
display::paint_point(p, theme::BG); display::paint_point(p, theme::BG);
} }
// BG - erasing the middle when not active // BG - erasing the middle when not full
if !active { if matches!(dot_type, DotType::Big) {
display::rect_fill(full_square.shrink(1), theme::BG) display::rect_fill(full_square.shrink(1), theme::BG)
} }
} }
DotType::Middle => {
let middle_square = full_square.shrink(1);
/// Drawing the dots horizontally and aligning to the right // 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)
}
}
}
/// 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<DotType, MAX_DOTS> {
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) { fn paint_horizontal(&mut self) {
let mut top_right = self.area.top_right(); let mut top_right = self.area.top_right();
// TODO: implement smaller dots - two more sizes for dot in self.get_drawable_dots().iter().rev() {
// TODO: implement showing at most MAX_DIGITS self.paint_dot(dot, top_right);
for i in (0..self.page_count).rev() {
self.paint_dot(i == self.active_page, top_right);
top_right.x -= Self::DOTS_INTERVAL; top_right.x -= Self::DOTS_INTERVAL;
top_right.print();
} }
} }
} }