1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-18 05:28:40 +00:00

fixup! refactor(core/rust): make choice page more reusable by allowing custom content

This commit is contained in:
tychovrahe 2022-11-10 19:57:57 +01:00
parent 3a93a28882
commit 1ce85119c3
7 changed files with 147 additions and 237 deletions

View File

@ -13,9 +13,6 @@ use super::{
};
use heapless::{String, Vec};
#[cfg(feature = "ui_debug")]
use crate::trace::Tracer;
pub enum Bip39EntryMsg {
ResultWord(String<15>),
}
@ -99,19 +96,11 @@ impl ChoiceFactoryBIP39 {
unreachable!()
}
}
fn get(&self, choice_index: u8) -> ChoiceItem {
if self.letter_choices.is_some() {
self.get_letter_item(choice_index)
} else if self.word_choices.is_some() {
self.get_word_item(choice_index)
} else {
unreachable!()
}
}
}
impl ChoiceFactory for ChoiceFactoryBIP39 {
type Item = ChoiceItem;
fn count(&self) -> u8 {
if let Some(letter_choices) = &self.letter_choices {
letter_choices.len() as u8
@ -122,29 +111,14 @@ impl ChoiceFactory for ChoiceFactoryBIP39 {
}
}
fn paint_center(&self, choice_index: u8, area: Rect, inverse: bool) {
self.get(choice_index).paint_center(area, inverse);
fn get(&self, choice_index: u8) -> ChoiceItem {
if self.letter_choices.is_some() {
self.get_letter_item(choice_index)
} else if self.word_choices.is_some() {
self.get_word_item(choice_index)
} else {
unreachable!()
}
fn width_center(&self, choice_index: u8) -> i16 {
self.get(choice_index).width_center()
}
fn paint_left(&self, choice_index: u8, area: Rect, show_incomplete: bool) -> Option<i16> {
self.get(choice_index).paint_left(area, show_incomplete)
}
fn paint_right(&self, choice_index: u8, area: Rect, show_incomplete: bool) -> Option<i16> {
self.get(choice_index).paint_right(area, show_incomplete)
}
fn btn_layout(&self, choice_index: u8) -> ButtonLayout<&'static str> {
self.get(choice_index).btn_layout()
}
#[cfg(feature = "ui_debug")]
fn trace(&self, t: &mut dyn Tracer, name: &str, choice_index: u8) {
t.field(name, &self.get(choice_index));
}
}

View File

@ -1,3 +1,5 @@
#[cfg(feature = "ui_debug")]
use crate::trace::Trace;
use crate::ui::{
component::{Child, Component, Event, EventCtx, Pad},
geometry::Rect,
@ -15,6 +17,22 @@ pub enum ChoicePageMsg {
const DEFAULT_ITEMS_DISTANCE: i16 = 10;
const DEFAULT_Y_BASELINE: i16 = 20;
pub trait Choice {
fn paint_center(&self, area: Rect, inverse: bool);
fn width_center(&self) -> i16 {
0
}
fn paint_left(&self, _area: Rect, _show_incomplete: bool) -> Option<i16> {
None
}
fn paint_right(&self, _area: Rect, _show_incomplete: bool) -> Option<i16> {
None
}
fn btn_layout(&self) -> ButtonLayout<&'static str> {
ButtonLayout::default_three_icons()
}
}
/// Interface for a specific component efficiently giving
/// `ChoicePage` all the information it needs to render
/// all the choice pages.
@ -25,23 +43,12 @@ const DEFAULT_Y_BASELINE: i16 = 20;
/// items only when they are needed, one-by-one.
/// This way, no more than one item is stored in memory at any time.
pub trait ChoiceFactory {
fn count(&self) -> u8;
fn paint_center(&self, choice_index: u8, area: Rect, inverse: bool);
fn width_center(&self, _choice_index: u8) -> i16 {
0
}
fn paint_left(&self, _choice_index: u8, _area: Rect, _show_incomplete: bool) -> Option<i16> {
None
}
fn paint_right(&self, _choice_index: u8, _area: Rect, _show_incomplete: bool) -> Option<i16> {
None
}
fn btn_layout(&self, _choice_index: u8) -> ButtonLayout<&'static str> {
ButtonLayout::default_three_icons()
}
#[cfg(feature = "ui_debug")]
fn trace(&self, t: &mut dyn crate::trace::Tracer, name: &str, choice_index: u8);
type Item: Choice + Trace;
#[cfg(not(feature = "ui_debug"))]
type Item: Choice;
fn count(&self) -> u8;
fn get(&self, index: u8) -> Self::Item;
}
/// General component displaying a set of items on the screen
@ -86,7 +93,7 @@ where
F: ChoiceFactory,
{
pub fn new(choices: F) -> Self {
let initial_btn_layout = choices.btn_layout(0);
let initial_btn_layout = choices.get(0).btn_layout();
Self {
choices,
@ -184,7 +191,7 @@ where
// Getting the remaining left and right areas.
let (left_area, _center_area, right_area) =
available_area.split_center(self.choices.width_center(self.page_counter as u8));
available_area.split_center(self.choices.get(self.page_counter as u8).width_center());
// Possibly drawing on the left side.
if self.has_previous_choice() || self.is_carousel {
@ -227,7 +234,8 @@ where
/// Display the current choice in the middle.
fn show_current_choice(&mut self, area: Rect) {
self.choices
.paint_center(self.page_counter, area, self.inverse_selected_item);
.get(self.page_counter)
.paint_center(area, self.inverse_selected_item);
// Color inversion is just one-time thing.
if self.inverse_selected_item {
@ -254,9 +262,10 @@ where
let current_area = area.split_right(x_offset + self.items_distance).0;
if let Some(width) =
self.choices
.paint_left(page_index as u8, current_area, self.show_incomplete)
if let Some(width) = self
.choices
.get(page_index as u8)
.paint_left(current_area, self.show_incomplete)
{
// Updating loop variables.
x_offset += width + self.items_distance;
@ -285,13 +294,14 @@ where
}
let current_area = area.split_left(x_offset + self.items_distance).1;
if let Some(width) =
self.choices
.paint_right(page_index as u8, current_area, self.show_incomplete)
if let Some(width) = self
.choices
.get(page_index as u8)
.paint_right(current_area, self.show_incomplete)
{
// Updating loop variables.
x_offset += width + self.items_distance;
page_index -= 1;
page_index += 1;
} else {
break;
}
@ -338,7 +348,7 @@ where
// and Cancel as HTC. PIN client would check if the PIN is empty/not and
// adjust the HTC/not.
let btn_layout = self.choices.btn_layout(self.page_counter);
let btn_layout = self.choices.get(self.page_counter).btn_layout();
self.buttons.mutate(ctx, |_ctx, buttons| {
buttons.set(btn_layout);
});
@ -429,21 +439,21 @@ where
t.kw_pair("is_carousel", booltostr!(self.is_carousel));
if self.has_previous_choice() {
self.choices.trace(t, "prev_choice", self.page_counter - 1);
t.field("prev_choice", &self.choices.get(self.page_counter - 1));
} else if self.is_carousel {
// In case of carousel going to the left end.
self.choices.trace(t, "prev_choice", self.last_page_index());
t.field("prev_choice", &self.choices.get(self.last_page_index()));
} else {
t.string("prev_choice");
t.symbol("None");
}
self.choices.trace(t, "current_choice", self.page_counter);
t.field("current_choice", &self.choices.get(self.page_counter));
if self.has_next_choice() {
self.choices.trace(t, "next_choice", self.page_counter + 1);
t.field("next_choice", &self.choices.get(self.page_counter + 1));
} else if self.is_carousel {
// In case of carousel going to the very left.
self.choices.trace(t, "next_choice", 0);
t.field("next_choice", &self.choices.get(0));
} else {
t.string("next_choice");
t.symbol("None");

View File

@ -1,7 +1,7 @@
use crate::ui::{
display::{rect_fill, rect_fill_corners, rect_outline_rounded, Font, Icon},
geometry::{Offset, Rect},
model_tr::theme,
model_tr::{component::choice::Choice, theme},
};
use heapless::String;
@ -49,17 +49,6 @@ impl ChoiceItem {
self.font.text_width(&self.text)
}
/// Getting the overall width in pixels when displayed in center.
/// That means both the icon and text will be shown.
pub fn width_center(&self) -> i16 {
let icon_width = if let Some(icon) = self.icon {
icon.width() + 2
} else {
0
};
self.text_width() + icon_width
}
/// Getting the non-central width in pixels.
/// It will show an icon if defined, otherwise the text, not both.
pub fn width_side(&self) -> i16 {
@ -95,25 +84,6 @@ impl ChoiceItem {
}
}
/// Painting the item as the main choice in the middle.
/// Showing both the icon and text, if the icon is available.
pub fn paint_center(&self, area: Rect, inverse: bool) {
self.paint_rounded_highlight(area, inverse);
let mut baseline = area.bottom_center() + Offset::new(-self.width_center() / 2, 0);
if let Some(icon) = self.icon {
let fg_color = if inverse { theme::BG } else { theme::FG };
let bg_color = if inverse { theme::FG } else { theme::BG };
icon.draw_bottom_left(baseline, fg_color, bg_color);
baseline = baseline + Offset::new(icon.width() + 2, 0);
}
if inverse {
display_inverse(baseline, &self.text, self.font);
} else {
display(baseline, &self.text, self.font);
}
}
/// Painting the item as a choice on the left side from center.
/// Showing only the icon, if available, otherwise the text.
pub fn render_left(&self, area: Rect) {
@ -124,24 +94,6 @@ impl ChoiceItem {
}
}
/// Painting item on the side if it fits, otherwise paint incomplete if
/// allowed
pub fn paint_left(&self, area: Rect, show_incomplete: bool) -> Option<i16> {
// When the item does not fit, we stop.
// Rendering the item anyway if the incomplete items are allowed.
if !self.fits(area) {
if show_incomplete {
self.render_left(area);
}
return None;
}
// Rendering the item.
self.render_left(area);
Some(self.width_side())
}
/// Painting the item as a choice on the right side from center.
/// Showing only the icon, if available, otherwise the text.
pub fn render_right(&self, area: Rect) {
@ -152,29 +104,6 @@ impl ChoiceItem {
}
}
/// Painting item on the side if it fits, otherwise paint incomplete if
/// allowed
pub fn paint_right(&self, area: Rect, show_incomplete: bool) -> Option<i16> {
// When the item does not fit, we stop.
// Rendering the item anyway if the incomplete items are allowed.
if !self.fits(area) {
if show_incomplete {
self.render_right(area);
}
return None;
}
// Rendering the item.
self.render_right(area);
Some(self.width_side())
}
/// Getting current button layout.
pub fn btn_layout(&self) -> ButtonLayout<&'static str> {
self.btn_layout.clone()
}
/// Setting left button.
pub fn set_left_btn(&mut self, btn_left: Option<ButtonDetails<&'static str>>) {
self.btn_layout.btn_left = btn_left;
@ -196,6 +125,78 @@ impl ChoiceItem {
}
}
impl Choice for ChoiceItem {
/// Painting the item as the main choice in the middle.
/// Showing both the icon and text, if the icon is available.
fn paint_center(&self, area: Rect, inverse: bool) {
self.paint_rounded_highlight(area, inverse);
let mut baseline = area.bottom_center() + Offset::new(-self.width_center() / 2, 0);
if let Some(icon) = self.icon {
let fg_color = if inverse { theme::BG } else { theme::FG };
let bg_color = if inverse { theme::FG } else { theme::BG };
icon.draw_bottom_left(baseline, fg_color, bg_color);
baseline = baseline + Offset::new(icon.width() + 2, 0);
}
if inverse {
display_inverse(baseline, &self.text, self.font);
} else {
display(baseline, &self.text, self.font);
}
}
/// Getting the overall width in pixels when displayed in center.
/// That means both the icon and text will be shown.
fn width_center(&self) -> i16 {
let icon_width = if let Some(icon) = self.icon {
icon.width() + 2
} else {
0
};
self.text_width() + icon_width
}
/// Painting item on the side if it fits, otherwise paint incomplete if
/// allowed
fn paint_left(&self, area: Rect, show_incomplete: bool) -> Option<i16> {
// When the item does not fit, we stop.
// Rendering the item anyway if the incomplete items are allowed.
if !self.fits(area) {
if show_incomplete {
self.render_left(area);
}
return None;
}
// Rendering the item.
self.render_left(area);
Some(self.width_side())
}
/// Painting item on the side if it fits, otherwise paint incomplete if
/// allowed
fn paint_right(&self, area: Rect, show_incomplete: bool) -> Option<i16> {
// When the item does not fit, we stop.
// Rendering the item anyway if the incomplete items are allowed.
if !self.fits(area) {
if show_incomplete {
self.render_right(area);
}
return None;
}
// Rendering the item.
self.render_right(area);
Some(self.width_side())
}
/// Getting current button layout.
fn btn_layout(&self) -> ButtonLayout<&'static str> {
self.btn_layout.clone()
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for ChoiceItem {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -44,7 +44,7 @@ pub use confirm::{HoldToConfirm, HoldToConfirmMsg};
pub use button_controller::{ButtonController, ButtonControllerMsg};
#[cfg(feature = "micropython")]
pub use changing_text::ChangingTextLine;
pub use choice::{ChoiceFactory, ChoicePage, ChoicePageMsg};
pub use choice::{Choice, ChoiceFactory, ChoicePage, ChoicePageMsg};
pub use choice_item::ChoiceItem;
pub use dialog::{Dialog, DialogMsg};
#[cfg(feature = "micropython")]

View File

@ -1,5 +1,3 @@
#[cfg(feature = "ui_debug")]
use crate::trace::Tracer;
use crate::{
time::Duration,
ui::{
@ -146,15 +144,10 @@ impl ChoiceFactoryPassphrase {
ChoiceItem::new(char_to_string::<1>(ch), ButtonLayout::default_three_icons())
}
}
fn get(&self, choice_index: u8) -> ChoiceItem {
match self.current_category {
ChoiceCategory::Menu => self.get_menu_item(choice_index),
_ => self.get_character_item(choice_index),
}
}
}
impl ChoiceFactory for ChoiceFactoryPassphrase {
type Item = ChoiceItem;
fn count(&self) -> u8 {
let length = get_category_length(&self.current_category);
// All non-MENU categories have an extra item for returning back to MENU
@ -163,29 +156,11 @@ impl ChoiceFactory for ChoiceFactoryPassphrase {
_ => length + 1,
}
}
fn paint_center(&self, choice_index: u8, area: Rect, inverse: bool) {
self.get(choice_index).paint_center(area, inverse);
fn get(&self, choice_index: u8) -> ChoiceItem {
match self.current_category {
ChoiceCategory::Menu => self.get_menu_item(choice_index),
_ => self.get_character_item(choice_index),
}
fn width_center(&self, choice_index: u8) -> i16 {
self.get(choice_index).width_center()
}
fn paint_left(&self, choice_index: u8, area: Rect, show_incomplete: bool) -> Option<i16> {
self.get(choice_index).paint_left(area, show_incomplete)
}
fn paint_right(&self, choice_index: u8, area: Rect, show_incomplete: bool) -> Option<i16> {
self.get(choice_index).paint_right(area, show_incomplete)
}
fn btn_layout(&self, choice_index: u8) -> ButtonLayout<&'static str> {
self.get(choice_index).btn_layout()
}
#[cfg(feature = "ui_debug")]
fn trace(&self, t: &mut dyn Tracer, name: &str, choice_index: u8) {
t.field(name, &self.get(choice_index));
}
}

View File

@ -13,7 +13,6 @@ use super::{
ButtonDetails, ButtonLayout, ChangingTextLine, ChoiceFactory, ChoiceItem, ChoicePage,
ChoicePageMsg,
};
use crate::trace::Tracer;
use heapless::String;
pub enum PinEntryMsg {
@ -53,6 +52,10 @@ impl ChoiceFactoryPIN {
fn new(prompt: StrBuffer) -> Self {
Self { prompt }
}
}
impl ChoiceFactory for ChoiceFactoryPIN {
type Item = ChoiceItem;
fn get(&self, choice_index: u8) -> ChoiceItem {
let choice_str = CHOICES[choice_index as usize];
@ -76,37 +79,10 @@ impl ChoiceFactoryPIN {
choice_item
}
}
impl ChoiceFactory for ChoiceFactoryPIN {
fn count(&self) -> u8 {
CHOICE_LENGTH as u8
}
fn paint_center(&self, choice_index: u8, area: Rect, inverse: bool) {
self.get(choice_index).paint_center(area, inverse);
}
fn width_center(&self, choice_index: u8) -> i16 {
self.get(choice_index).width_center()
}
fn paint_left(&self, choice_index: u8, area: Rect, show_incomplete: bool) -> Option<i16> {
self.get(choice_index).paint_left(area, show_incomplete)
}
fn paint_right(&self, choice_index: u8, area: Rect, show_incomplete: bool) -> Option<i16> {
self.get(choice_index).paint_right(area, show_incomplete)
}
fn btn_layout(&self, choice_index: u8) -> ButtonLayout<&'static str> {
self.get(choice_index).btn_layout()
}
#[cfg(feature = "ui_debug")]
fn trace(&self, t: &mut dyn Tracer, name: &str, choice_index: u8) {
t.field(name, &self.get(choice_index));
}
}
/// Component for entering a PIN.

View File

@ -6,9 +6,6 @@ use crate::ui::{
use super::{ButtonLayout, ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg};
use heapless::{String, Vec};
#[cfg(feature = "ui_debug")]
use crate::trace::Tracer;
#[cfg(feature = "ui_debug")]
use super::{ButtonAction, ButtonPos};
@ -28,6 +25,17 @@ where
fn new(choices: Vec<T, N>, carousel: bool) -> Self {
Self { choices, carousel }
}
}
impl<T, const N: usize> ChoiceFactory for ChoiceFactorySimple<T, N>
where
T: AsRef<str>,
{
type Item = ChoiceItem;
fn count(&self) -> u8 {
N as u8
}
fn get(&self, choice_index: u8) -> ChoiceItem {
let text = &self.choices[choice_index as usize];
@ -46,40 +54,6 @@ where
}
}
impl<T, const N: usize> ChoiceFactory for ChoiceFactorySimple<T, N>
where
T: AsRef<str>,
{
fn count(&self) -> u8 {
N as u8
}
fn paint_center(&self, choice_index: u8, area: Rect, inverse: bool) {
self.get(choice_index).paint_center(area, inverse);
}
fn width_center(&self, choice_index: u8) -> i16 {
self.get(choice_index).width_center()
}
fn paint_left(&self, choice_index: u8, area: Rect, show_incomplete: bool) -> Option<i16> {
self.get(choice_index).paint_left(area, show_incomplete)
}
fn paint_right(&self, choice_index: u8, area: Rect, show_incomplete: bool) -> Option<i16> {
self.get(choice_index).paint_right(area, show_incomplete)
}
fn btn_layout(&self, choice_index: u8) -> ButtonLayout<&'static str> {
self.get(choice_index).btn_layout()
}
#[cfg(feature = "ui_debug")]
fn trace(&self, t: &mut dyn Tracer, name: &str, choice_index: u8) {
t.field(name, &self.get(choice_index));
}
}
/// Simple wrapper around `ChoicePage` that allows for
/// inputting a list of values and receiving the chosen one.
pub struct SimpleChoice<T, const N: usize>