diff --git a/core/embed/rust/src/ui/model_tr/component/button.rs b/core/embed/rust/src/ui/model_tr/component/button.rs index 8ad0e949bd..bf4585f968 100644 --- a/core/embed/rust/src/ui/model_tr/component/button.rs +++ b/core/embed/rust/src/ui/model_tr/component/button.rs @@ -497,11 +497,11 @@ impl ButtonLayout { /// Default button layout for all three buttons - icons. pub fn default_three_icons() -> Self { - Self::arrow_armed_icon("SELECT".into()) + Self::arrow_armed_arrow("SELECT".into()) } /// Special middle text for default icon layout. - pub fn arrow_armed_icon(text: StrBuffer) -> Self { + pub fn arrow_armed_arrow(text: StrBuffer) -> Self { Self::new( Some(ButtonDetails::left_arrow_icon()), Some(ButtonDetails::armed_text(text)), @@ -518,6 +518,15 @@ impl ButtonLayout { ) } + /// Middle armed text and next right arrow. + pub fn none_armed_arrow(text: StrBuffer) -> Self { + Self::new( + None, + Some(ButtonDetails::armed_text(text)), + Some(ButtonDetails::right_arrow_icon()), + ) + } + /// Left cancel, armed text and right text. pub fn cancel_armed_text(middle: StrBuffer, right: StrBuffer) -> Self { Self::new( diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs index e6919642f5..77d3df4f7c 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs @@ -156,12 +156,12 @@ where &mut self, ctx: &mut EventCtx, new_choices: F, - reset_page_counter: bool, + new_page_counter: Option, is_carousel: bool, ) { self.choices = new_choices; - if reset_page_counter { - self.page_counter = 0; + if let Some(new_counter) = new_page_counter { + self.page_counter = new_counter; } self.is_carousel = is_carousel; self.update(ctx); diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs index 7784e11596..33262ef2ed 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs @@ -135,7 +135,7 @@ impl ChoiceFactoryPassphrase { /// return back fn get_character_item(&self, choice_index: u8) -> ChoiceItem { if is_menu_choice(&self.current_category, choice_index) { - ChoiceItem::new("MENU", ButtonLayout::arrow_armed_icon("RETURN".into())) + ChoiceItem::new("MENU", ButtonLayout::arrow_armed_arrow("RETURN".into())) } else { let ch = get_char(&self.current_category, choice_index); ChoiceItem::new(char_to_string::<1>(ch), ButtonLayout::default_three_icons()) @@ -211,7 +211,7 @@ impl PassphraseEntry { /// Displaying the MENU fn show_menu_page(&mut self, ctx: &mut EventCtx) { let menu_choices = ChoiceFactoryPassphrase::new(ChoiceCategory::Menu); - self.choice_page.reset(ctx, menu_choices, true, false); + self.choice_page.reset(ctx, menu_choices, Some(0), false); // Going back to the last MENU position before showing the MENU self.choice_page.set_page_counter(ctx, self.menu_position); } @@ -219,7 +219,7 @@ impl PassphraseEntry { /// Displaying the character category fn show_category_page(&mut self, ctx: &mut EventCtx) { let category_choices = ChoiceFactoryPassphrase::new(self.current_category.clone()); - self.choice_page.reset(ctx, category_choices, true, true); + self.choice_page.reset(ctx, category_choices, Some(0), true); } pub fn passphrase(&self) -> &str { diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs index 513e3c2243..abb433bdbe 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs @@ -24,6 +24,12 @@ const MAX_LETTERS_LENGTH: usize = 26; /// Offer words when there will be fewer of them than this const OFFER_WORDS_THRESHOLD: usize = 10; +/// Where will be the DELETE option - at the first position +const DELETE_INDEX: u8 = 0; +/// Which index will be used at the beginning. +/// (Accounts for DELETE to be at index 0) +const INITIAL_PAGE_COUNTER: u8 = DELETE_INDEX + 1; + const PROMPT: &str = "_"; /// Type of the wordlist, deciding the list of words to be used @@ -61,13 +67,15 @@ impl ChoiceFactory for ChoiceFactoryWordlist { fn get(&self, choice_index: u8) -> ChoiceItem { // Letters have a carousel, words do not + // Putting DELETE as the first option in both cases + // (is a requirement for WORDS, doing it for LETTERS as well to unite it) match self { Self::Letters(letter_choices) => { - if choice_index >= letter_choices.len() as u8 { - ChoiceItem::new("DELETE", ButtonLayout::arrow_armed_icon("CONFIRM".into())) + if choice_index == DELETE_INDEX { + ChoiceItem::new("DELETE", ButtonLayout::arrow_armed_arrow("CONFIRM".into())) .with_icon(Icon::new(theme::ICON_DELETE)) } else { - let letter = letter_choices[choice_index as usize]; + let letter = letter_choices[choice_index as usize - 1]; ChoiceItem::new( char_to_string::<1>(letter), ButtonLayout::default_three_icons(), @@ -75,17 +83,14 @@ impl ChoiceFactory for ChoiceFactoryWordlist { } } Self::Words(word_choices) => { - if choice_index >= word_choices.len() as u8 { - let mut item = - ChoiceItem::new("DELETE", ButtonLayout::arrow_armed_icon("CONFIRM".into())) - .with_icon(Icon::new(theme::ICON_DELETE)); - item.set_right_btn(None); - item + if choice_index == DELETE_INDEX { + ChoiceItem::new("DELETE", ButtonLayout::none_armed_arrow("CONFIRM".into())) + .with_icon(Icon::new(theme::ICON_DELETE)) } else { - let word = word_choices[choice_index as usize]; + let word = word_choices[choice_index as usize - 1]; let mut item = ChoiceItem::new(word, ButtonLayout::default_three_icons()); - if choice_index == 0 { - item.set_left_btn(None); + if choice_index == self.count() - 1 { + item.set_right_btn(None); } item } @@ -113,9 +118,11 @@ impl WordlistEntry { let choices = ChoiceFactoryWordlist::letters(letter_choices.clone()); Self { + // Starting at second page because of DELETE option choice_page: ChoicePage::new(choices) .with_incomplete(true) - .with_carousel(true), + .with_carousel(true) + .with_initial_page_counter(INITIAL_PAGE_COUNTER), chosen_letters: Child::new(ChangingTextLine::center_mono(String::from(PROMPT))), letter_choices, textbox: TextBox::empty(), @@ -160,8 +167,12 @@ impl WordlistEntry { let new_choices = self.get_current_choices(); // Not using carousel in case of words, as that looks weird in case // there is only one word to choose from. - self.choice_page - .reset(ctx, new_choices, true, !self.offer_words); + self.choice_page.reset( + ctx, + new_choices, + Some(INITIAL_PAGE_COUNTER), + !self.offer_words, + ); ctx.request_paint(); } @@ -211,21 +222,21 @@ impl Component for WordlistEntry { // Clicked SELECT. // When we already offer words, return the word at the given index. // Otherwise, resetting the choice page with up-to-date choices. - if page_counter as usize == self.delete_index() { + if page_counter == DELETE_INDEX { // Clicked DELETE. Deleting last letter, updating wordlist and updating choices self.delete_last_letter(ctx); self.reset_wordlist(); self.update(ctx); - } else if self.offer_words { - let word = self - .words_list - .get(page_counter as usize) - .unwrap_or_default(); - return Some(WordlistEntryMsg::ResultWord(String::from(word))); } else { - let new_letter = self.letter_choices[page_counter as usize]; - self.append_letter(ctx, new_letter); - self.update(ctx); + let index = page_counter as usize - 1; + if self.offer_words { + let word = self.words_list.get(index).unwrap_or_default(); + return Some(WordlistEntryMsg::ResultWord(String::from(word))); + } else { + let new_letter = self.letter_choices[index]; + self.append_letter(ctx, new_letter); + self.update(ctx); + } } } @@ -252,16 +263,16 @@ impl crate::trace::Trace for WordlistEntry { ButtonPos::Left => ButtonAction::PrevPage.string(), ButtonPos::Right => ButtonAction::NextPage.string(), ButtonPos::Middle => { - let current_index = self.choice_page.page_index() as usize; - let choice: String<10> = if current_index == self.delete_index() { + let current_index = self.choice_page.page_index(); + let choice: String<10> = if current_index == DELETE_INDEX { String::from("DELETE") - } else if self.offer_words { - self.words_list - .get(current_index) - .unwrap_or_default() - .into() } else { - util::char_to_string(self.letter_choices[current_index]) + let index = current_index as usize - 1; + if self.offer_words { + self.words_list.get(index).unwrap_or_default().into() + } else { + util::char_to_string(self.letter_choices[index]) + } }; ButtonAction::select_item(choice) } diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index e227d78497..f63f5a5542 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -509,7 +509,7 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map } else { // Page in the middle ( - ButtonLayout::arrow_armed_icon("SELECT".into()), + ButtonLayout::arrow_armed_arrow("SELECT".into()), ButtonActions::prev_confirm_next(), ) }; diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index f8ae58e727..d5e5f8610d 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -1309,7 +1309,7 @@ async def pin_mismatch( ctx, br_type, "PIN MISMATCH", - "The PINs you entered do not match.\nPlease try again.", + description="The PINs you entered do not match.\nPlease try again.", verb="TRY AGAIN", verb_cancel=None, br_code=br_code,