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:
parent
ed4395b386
commit
b84322e4e9
@ -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(),
|
|
||||||
Offset::new(
|
|
||||||
-self.scrollbar.inner().overall_width(),
|
|
||||||
ScrollBar::MAX_DOT_SIZE,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
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);
|
self.buttons.place(button_area);
|
||||||
|
@ -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
|
||||||
|
@ -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,33 +81,129 @@ 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));
|
||||||
|
|
||||||
// FG - painting the full square
|
match dot_type {
|
||||||
display::rect_fill(full_square, theme::FG);
|
DotType::BigFull | DotType::Big => {
|
||||||
|
// FG - painting the full square
|
||||||
|
display::rect_fill(full_square, theme::FG);
|
||||||
|
|
||||||
// BG - erase four corners
|
// BG - erase four corners
|
||||||
for p in full_square.corner_points().iter() {
|
for p in full_square.corner_points().iter() {
|
||||||
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);
|
||||||
|
|
||||||
|
// 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<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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user