mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-09 23:11:10 +00:00
WIP: feat(lincoln): hint component
This commit is contained in:
parent
1255c3ae2d
commit
0f89fa9bdf
210
core/embed/rust/src/ui/model_lincoln/component/hint.rs
Normal file
210
core/embed/rust/src/ui/model_lincoln/component/hint.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{text::TextStyle, Component, EventCtx, Never},
|
||||
display::{Color, Font, Icon},
|
||||
geometry::{Alignment, Alignment2D, Direction, Offset, Point, Rect},
|
||||
shape::{self, Renderer, Text},
|
||||
},
|
||||
};
|
||||
|
||||
use super::theme;
|
||||
|
||||
/// Component rendered above ActionBar, showing one of these:
|
||||
/// - a task instruction/hint, e.g. "Confirm transaction"
|
||||
/// - a page counter e.g. "1 / 3", meaning the first screen of three total.
|
||||
#[derive(Clone)]
|
||||
pub struct Hint<'a> {
|
||||
area: Rect,
|
||||
content: HintContent<'a>,
|
||||
swipe_allow_up: bool,
|
||||
swipe_allow_down: bool,
|
||||
progress: i16,
|
||||
dir: Direction,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum HintContent<'a> {
|
||||
Instruction(Instruction<'a>),
|
||||
PageCounter(PageCounter),
|
||||
}
|
||||
|
||||
impl<'a> Hint<'a> {
|
||||
/// height of the single line component [px]
|
||||
pub const HEIGHT_SINGLE_LINE: i16 = 40;
|
||||
/// height of the multi line component [px]
|
||||
pub const HEIGHT_MULTI_LINE: i16 = 60;
|
||||
|
||||
fn from_content(content: HintContent<'a>) -> Self {
|
||||
Self {
|
||||
area: Rect::zero(),
|
||||
content,
|
||||
swipe_allow_down: false,
|
||||
swipe_allow_up: false,
|
||||
progress: 0,
|
||||
dir: Direction::Up,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_instruction<T: Into<TString<'a>>>(
|
||||
instruction: T,
|
||||
color: Color,
|
||||
icon: Option<Icon>,
|
||||
) -> Self {
|
||||
let instruction = instruction.into();
|
||||
let instruction_component = Instruction::new(instruction, color, icon);
|
||||
Self::from_content(HintContent::Instruction(instruction_component))
|
||||
}
|
||||
|
||||
pub fn new_page_counter() -> Self {
|
||||
Self::from_content(HintContent::PageCounter(PageCounter::new()))
|
||||
}
|
||||
|
||||
pub fn update_page_counter(&mut self, ctx: &mut EventCtx, current: usize, max: usize) {
|
||||
match &mut self.content {
|
||||
HintContent::PageCounter(counter) => {
|
||||
counter.update_current_page(current, max);
|
||||
self.swipe_allow_down = counter.is_first_page();
|
||||
self.swipe_allow_up = counter.is_last_page();
|
||||
ctx.request_paint();
|
||||
}
|
||||
_ => {
|
||||
#[cfg(feature = "ui_debug")]
|
||||
panic!("hint component does not have counter")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> i16 {
|
||||
self.content.height()
|
||||
}
|
||||
|
||||
pub fn with_swipe(self, swipe_direction: Direction) -> Self {
|
||||
match swipe_direction {
|
||||
Direction::Up => Self {
|
||||
swipe_allow_up: true,
|
||||
..self
|
||||
},
|
||||
Direction::Down => Self {
|
||||
swipe_allow_down: true,
|
||||
..self
|
||||
},
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a> Component for Hint<'a> {
|
||||
// type Msg = Never;
|
||||
// }
|
||||
|
||||
|
||||
impl<'a> HintContent<'a> {
|
||||
fn height(&self) -> i16 {
|
||||
if matches!(self, HintContent::PageCounter(_)) {
|
||||
Hint::HEIGHT_SINGLE_LINE
|
||||
} else {
|
||||
// TODO: determine height based on text length
|
||||
Hint::HEIGHT_MULTI_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper componet used within Hint for instruction/hint rendering.
|
||||
#[derive(Clone)]
|
||||
struct Instruction<'a> {
|
||||
text: TString<'a>,
|
||||
color: Color,
|
||||
icon: Option<Icon>,
|
||||
}
|
||||
|
||||
impl<'a> Instruction<'a> {
|
||||
const STYLE_INSTRUCTION: &'static TextStyle = &theme::TEXT_NORMAL;
|
||||
|
||||
fn new(text: TString<'a>, color: Color, icon: Option<Icon>) -> Self {
|
||||
Self { text, color, icon }
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper component used within Hint for page count indication, rendered e.g. as: '1 / 20'.
|
||||
#[derive(Clone)]
|
||||
struct PageCounter {
|
||||
page_curr: u8,
|
||||
page_max: u8,
|
||||
}
|
||||
|
||||
impl PageCounter {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
page_curr: 0,
|
||||
page_max: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_current_page(&mut self, new_value: usize, max: usize) {
|
||||
self.page_max = max as u8;
|
||||
self.page_curr = (new_value as u8).clamp(0, self.page_max.saturating_sub(1));
|
||||
}
|
||||
|
||||
fn is_first_page(&self) -> bool {
|
||||
self.page_curr == 0
|
||||
}
|
||||
|
||||
fn is_last_page(&self) -> bool {
|
||||
self.page_curr + 1 == self.page_max
|
||||
}
|
||||
}
|
||||
|
||||
impl PageCounter {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>, area: Rect) {
|
||||
let font = Font::SUB;
|
||||
let color = if self.is_last_page() {
|
||||
theme::GREEN_LIGHT
|
||||
} else {
|
||||
theme::GREY
|
||||
};
|
||||
|
||||
let string_curr = uformat!("{}", self.page_curr + 1);
|
||||
let string_max = uformat!("{}", self.page_max);
|
||||
|
||||
// center the whole counter "x / yz"
|
||||
let offset_x = Offset::x(4); // spacing between foreslash and numbers
|
||||
let width_num_curr = font.text_width(&string_curr);
|
||||
let width_foreslash = theme::ICON_FORESLASH.toif.width();
|
||||
let width_num_max = font.text_width(&string_max);
|
||||
let width_total = width_num_curr + width_foreslash + width_num_max + 2 * offset_x.x;
|
||||
|
||||
let counter_area = area.split_top(Hint::HEIGHT_SINGLE_LINE).0;
|
||||
let center_x = counter_area.center().x;
|
||||
let counter_y = font.vert_center(counter_area.y0, counter_area.y1, "0");
|
||||
let counter_start_x = center_x - width_total / 2;
|
||||
let counter_end_x = center_x + width_total / 2;
|
||||
let base_num_curr = Point::new(counter_start_x, counter_y);
|
||||
let base_foreslash = Point::new(counter_start_x + width_num_curr + offset_x.x, counter_y);
|
||||
let base_num_max = Point::new(counter_end_x, counter_y);
|
||||
|
||||
Text::new(base_num_curr, &string_curr)
|
||||
.with_align(Alignment::Start)
|
||||
.with_fg(color)
|
||||
.with_font(font)
|
||||
.render(target);
|
||||
shape::ToifImage::new(base_foreslash, theme::ICON_FORESLASH.toif)
|
||||
.with_align(Alignment2D::BOTTOM_LEFT)
|
||||
.with_fg(color)
|
||||
.render(target);
|
||||
Text::new(base_num_max, &string_max)
|
||||
.with_align(Alignment::End)
|
||||
.with_fg(color)
|
||||
.with_font(font)
|
||||
.render(target);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for PageCounter {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("PageCounter");
|
||||
t.int("page current", self.page_curr.into());
|
||||
t.int("page max", self.page_max.into());
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
mod button;
|
||||
mod error;
|
||||
mod header;
|
||||
mod hint;
|
||||
mod result;
|
||||
mod welcome_screen;
|
||||
|
||||
pub use button::{ButtonStyle, ButtonStyleSheet};
|
||||
pub use error::ErrorScreen;
|
||||
pub use header::Header;
|
||||
pub use hint::Hint;
|
||||
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
||||
pub use welcome_screen::WelcomeScreen;
|
||||
|
||||
|
@ -40,6 +40,7 @@ pub const FATAL_ERROR_HIGHLIGHT_COLOR: Color = Color::rgb(0xFF, 0x41, 0x41);
|
||||
|
||||
// UI icons (white color).
|
||||
// TODO: icons
|
||||
include_icon!(ICON_FORESLASH, "model_mercury/res/foreslash12.toif"); // FIXME: mercury icon
|
||||
include_icon!(ICON_ASTERISK, "model_lincoln/res/asterisk20.toif");
|
||||
include_icon!(ICON_CHECKMARK, "model_lincoln/res/checkmark24.toif");
|
||||
include_icon!(ICON_CHEVRON_DOWN, "model_lincoln/res/chevron_down24.toif");
|
||||
|
@ -15,7 +15,8 @@ use crate::{
|
||||
/// Component showing a task instruction, e.g. "Swipe up", and an optional
|
||||
/// content consisting of one of these:
|
||||
/// - a task description e.g. "Confirm transaction", or
|
||||
/// - a page counter e.g. "1 / 3", meaning the first screen of three total.
|
||||
/// - a page counter e.g. "1 / 3", meaning the first screen of three total, or
|
||||
/// - a page hint e.g. "Go back" if you are on the last page.
|
||||
/// A host of this component is responsible of providing the exact area
|
||||
/// considering also the spacing. The height must be 18px (only instruction) or
|
||||
/// 37px (instruction and description/position).
|
||||
@ -313,7 +314,6 @@ impl<'a> FooterContent<'a> {
|
||||
#[derive(Clone)]
|
||||
struct PageCounter {
|
||||
pub instruction: TString<'static>,
|
||||
font: Font,
|
||||
page_curr: u8,
|
||||
page_max: u8,
|
||||
}
|
||||
@ -324,7 +324,6 @@ impl PageCounter {
|
||||
instruction,
|
||||
page_curr: 0,
|
||||
page_max: 0,
|
||||
font: Font::SUB,
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,6 +343,7 @@ impl PageCounter {
|
||||
|
||||
impl PageCounter {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>, area: Rect) {
|
||||
let font = Font::SUB;
|
||||
let color = if self.is_last_page() {
|
||||
theme::GREEN_LIGHT
|
||||
} else {
|
||||
@ -355,14 +355,14 @@ impl PageCounter {
|
||||
|
||||
// center the whole counter "x / yz"
|
||||
let offset_x = Offset::x(4); // spacing between foreslash and numbers
|
||||
let width_num_curr = self.font.text_width(&string_curr);
|
||||
let width_num_curr = font.text_width(&string_curr);
|
||||
let width_foreslash = theme::ICON_FORESLASH.toif.width();
|
||||
let width_num_max = self.font.text_width(&string_max);
|
||||
let width_num_max = font.text_width(&string_max);
|
||||
let width_total = width_num_curr + width_foreslash + width_num_max + 2 * offset_x.x;
|
||||
|
||||
let counter_area = area.split_top(Footer::HEIGHT_SIMPLE).0;
|
||||
let center_x = counter_area.center().x;
|
||||
let counter_y = self.font.vert_center(counter_area.y0, counter_area.y1, "0");
|
||||
let counter_y = font.vert_center(counter_area.y0, counter_area.y1, "0");
|
||||
let counter_start_x = center_x - width_total / 2;
|
||||
let counter_end_x = center_x + width_total / 2;
|
||||
let base_num_curr = Point::new(counter_start_x, counter_y);
|
||||
@ -372,7 +372,7 @@ impl PageCounter {
|
||||
Text::new(base_num_curr, &string_curr)
|
||||
.with_align(Alignment::Start)
|
||||
.with_fg(color)
|
||||
.with_font(self.font)
|
||||
.with_font(font)
|
||||
.render(target);
|
||||
shape::ToifImage::new(base_foreslash, theme::ICON_FORESLASH.toif)
|
||||
.with_align(Alignment2D::BOTTOM_LEFT)
|
||||
@ -381,7 +381,7 @@ impl PageCounter {
|
||||
Text::new(base_num_max, &string_max)
|
||||
.with_align(Alignment::End)
|
||||
.with_fg(color)
|
||||
.with_font(self.font)
|
||||
.with_font(font)
|
||||
.render(target);
|
||||
|
||||
FooterContent::render_instruction(target, area, &self.instruction);
|
||||
|
Loading…
Reference in New Issue
Block a user