diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/bip39.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/bip39.rs index 81e01c2fc7..f1459d71b1 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/bip39.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/bip39.rs @@ -301,7 +301,7 @@ impl Bip39Input { // Auto-complete button. self.button.enable(ctx); self.button - .set_stylesheet(ctx, theme::button_pin_autocomplete()); + .set_stylesheet(ctx, theme::button_bip39_autocomplete()); self.button .set_content(ctx, ButtonContent::Icon(theme::ICON_AUTOFILL)); self.button_suggestion @@ -310,7 +310,7 @@ impl Bip39Input { } else { // Disabled button. self.button.disable(ctx); - self.button.set_stylesheet(ctx, theme::button_pin()); + self.button.set_stylesheet(ctx, theme::button_keyboard()); self.button.set_content(ctx, ButtonContent::Text("".into())); } } diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/mnemonic.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/mnemonic.rs index 839dbbcd91..2a5ad0829a 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/mnemonic.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/mnemonic.rs @@ -58,7 +58,7 @@ where keys: T::keys() .map(|t| { Button::with_text(t.into()) - .styled(theme::button_pin()) + .styled(theme::button_keyboard()) .with_text_align(Alignment::Center) }) .map(Child::new), diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs index 304f13339b..ed7aa26c35 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs @@ -67,7 +67,7 @@ impl PassphraseKeyboard { .with_long_press(theme::ERASE_HOLD_DURATION) .into_child(), keys: KEYBOARD[STARTING_PAGE].map(|text| { - Child::new(Button::new(Self::key_content(text)).styled(theme::button_pin())) + Child::new(Button::new(Self::key_content(text)).styled(theme::button_keyboard())) }), scrollbar: ScrollBar::horizontal(), fade: Cell::new(false), diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs index b3be2deb62..f85ced9732 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs @@ -10,7 +10,7 @@ use crate::{ base::ComponentExt, text::TextStyle, Child, Component, Event, EventCtx, Label, Maybe, Never, Pad, TimerToken, }, - display::{self, Font}, + display::Font, event::TouchEvent, geometry::{Alignment, Alignment2D, Grid, Insets, Offset, Rect}, model_mercury::component::{ @@ -28,15 +28,15 @@ pub enum PinKeyboardMsg { } const MAX_LENGTH: usize = 50; -const MAX_VISIBLE_DOTS: usize = 14; -const MAX_VISIBLE_DIGITS: usize = 16; +const MAX_VISIBLE_DOTS: usize = 18; +const MAX_VISIBLE_DIGITS: usize = 18; const DIGIT_COUNT: usize = 10; // 0..10 -const HEADER_PADDING_SIDE: i16 = 5; -const HEADER_PADDING_BOTTOM: i16 = 12; +const HEADER_PADDING_SIDE: i16 = 2; +const HEADER_PADDING_BOTTOM: i16 = 2; const HEADER_PADDING: Insets = Insets::new( - theme::borders().top, + 0, HEADER_PADDING_SIDE, HEADER_PADDING_BOTTOM, HEADER_PADDING_SIDE, @@ -57,10 +57,6 @@ pub struct PinKeyboard<'a> { } impl<'a> PinKeyboard<'a> { - // Label position fine-tuning. - const MAJOR_OFF: Offset = Offset::y(11); - const MINOR_OFF: Offset = Offset::y(11); - pub fn new( major_prompt: TString<'a>, minor_prompt: TString<'a>, @@ -68,17 +64,13 @@ impl<'a> PinKeyboard<'a> { allow_cancel: bool, ) -> Self { // Control buttons. - let erase_btn = Button::with_icon_blend( - theme::IMAGE_BG_BACK_BTN, - theme::ICON_BACK, - Offset::new(30, 12), - ) - .styled(theme::button_reset()) - .with_long_press(theme::ERASE_HOLD_DURATION) - .initially_enabled(false); + let erase_btn = Button::with_icon(theme::ICON_DELETE) + .styled(theme::button_pin_erase()) + .with_long_press(theme::ERASE_HOLD_DURATION) + .initially_enabled(false); let erase_btn = Maybe::hidden(theme::BG, erase_btn).into_child(); - let cancel_btn = Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel()); + let cancel_btn = Button::with_icon(theme::ICON_CLOSE).styled(theme::button_pin_cancel()); let cancel_btn = Maybe::new(theme::BG, cancel_btn, allow_cancel).into_child(); Self { @@ -94,7 +86,7 @@ impl<'a> PinKeyboard<'a> { erase_btn, cancel_btn, confirm_btn: Button::with_icon(theme::ICON_CONFIRM) - .styled(theme::button_confirm()) + .styled(theme::button_pin_confirm()) .initially_enabled(false) .into_child(), digit_btns: Self::generate_digit_buttons(), @@ -108,7 +100,10 @@ impl<'a> PinKeyboard<'a> { random::shuffle(&mut digits); digits .map(|c| Button::with_text(c.into())) - .map(|b| b.styled(theme::button_pin())) + .map(|b| { + b.styled(theme::button_keyboard()) + .with_text_align(Alignment::Center) + }) .map(Child::new) } @@ -150,20 +145,10 @@ impl Component for PinKeyboard<'_> { type Msg = PinKeyboardMsg; fn place(&mut self, bounds: Rect) -> Rect { - // Ignore the top padding for now, we need it to reliably register textbox touch - // events. - let borders_no_top = Insets { - top: 0, - ..theme::borders() - }; // Prompts and PIN dots display. - let (header, keypad) = bounds - .inset(borders_no_top) - .split_bottom(4 * theme::PIN_BUTTON_HEIGHT + 3 * theme::BUTTON_SPACING); + let (header, keypad) = + bounds.split_bottom(4 * theme::PIN_BUTTON_HEIGHT + 3 * theme::BUTTON_SPACING); let prompt = header.inset(HEADER_PADDING); - // the inset -3 is a workaround for long text in "re-enter wipe code" - let major_area = prompt.translate(Self::MAJOR_OFF).inset(Insets::right(-3)); - let minor_area = prompt.translate(Self::MINOR_OFF); // Control buttons. let grid = Grid::new(keypad, 4, 3).with_spacing(theme::BUTTON_SPACING); @@ -171,9 +156,9 @@ impl Component for PinKeyboard<'_> { // Prompts and PIN dots display. self.textbox_pad.place(header); self.textbox.place(header); - self.major_prompt.place(major_area); - self.minor_prompt.place(minor_area); - self.major_warning.as_mut().map(|c| c.place(major_area)); + self.major_prompt.place(prompt); + self.minor_prompt.place(prompt); + self.major_warning.as_mut().map(|c| c.place(prompt)); // Control buttons. let erase_cancel_area = grid.row_col(3, 0); @@ -269,6 +254,7 @@ impl Component for PinKeyboard<'_> { fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { self.erase_btn.render(target); self.textbox_pad.render(target); + if self.textbox.inner().is_empty() { if let Some(ref w) = self.major_warning { w.render(target); @@ -280,7 +266,9 @@ impl Component for PinKeyboard<'_> { } else { self.textbox.render(target); } + self.confirm_btn.render(target); + for btn in &self.digit_btns { btn.render(target); } @@ -310,7 +298,7 @@ struct PinDots { impl PinDots { const DOT: i16 = 6; - const PADDING: i16 = 6; + const PADDING: i16 = 7; const TWITCH: i16 = 4; fn new(style: TextStyle) -> Self { @@ -361,128 +349,63 @@ impl PinDots { &self.digits } - fn paint_digits(&self, area: Rect) { - let center = area.center() + Offset::y(Font::MONO.text_height() / 2); - let right = center + Offset::x(Font::MONO.text_width("0") * (MAX_VISIBLE_DOTS as i16) / 2); - let digits = self.digits.len(); - - if digits <= MAX_VISIBLE_DOTS { - display::text_center( - center, - &self.digits, - Font::MONO, - self.style.text_color, - self.style.background_color, - ); - } else { - let offset: usize = digits.saturating_sub(MAX_VISIBLE_DIGITS); - display::text_right( - right, - &self.digits[offset..], - Font::MONO, - self.style.text_color, - self.style.background_color, - ); - } - } - fn render_digits<'s>(&self, area: Rect, target: &mut impl Renderer<'s>) { - let center = area.center() + Offset::y(Font::MONO.text_height() / 2); - let right = center + Offset::x(Font::MONO.text_width("0") * (MAX_VISIBLE_DOTS as i16) / 2); + let left = area.left_center() + Offset::y(Font::MONO.visible_text_height("1") / 2); let digits = self.digits.len(); - if digits <= MAX_VISIBLE_DOTS { - shape::Text::new(center, &self.digits) - .with_align(Alignment::Center) + if digits <= MAX_VISIBLE_DIGITS { + shape::Text::new(left, &self.digits) + .with_align(Alignment::Start) .with_font(Font::MONO) .with_fg(self.style.text_color) .render(target); } else { let offset: usize = digits.saturating_sub(MAX_VISIBLE_DIGITS); - shape::Text::new(right, &self.digits[offset..]) - .with_align(Alignment::End) + shape::Text::new(left, &self.digits[offset..]) + .with_align(Alignment::Start) .with_font(Font::MONO) .with_fg(self.style.text_color) .render(target); } } - fn paint_dots(&self, area: Rect) { - let mut cursor = self.size().snap(area.center(), Alignment2D::CENTER); - - let digits = self.digits.len(); - let dots_visible = digits.min(MAX_VISIBLE_DOTS); - let step = Self::DOT + Self::PADDING; - - // Jiggle when overflowed. - if digits > dots_visible && digits % 2 == 0 { - cursor.x += Self::TWITCH - } - - // Small leftmost dot. - if digits > dots_visible + 1 { - theme::DOT_SMALL.draw( - cursor - Offset::x(2 * step), - Alignment2D::TOP_LEFT, - self.style.text_color, - self.style.background_color, - ); - } - - // Greyed out dot. - if digits > dots_visible { - theme::DOT_ACTIVE.draw( - cursor - Offset::x(step), - Alignment2D::TOP_LEFT, - theme::GREY_LIGHT, - self.style.background_color, - ); - } - - // Draw a dot for each PIN digit. - for _ in 0..dots_visible { - theme::DOT_ACTIVE.draw( - cursor, - Alignment2D::TOP_LEFT, - self.style.text_color, - self.style.background_color, - ); - cursor.x += step; - } - } - fn render_dots<'s>(&self, area: Rect, target: &mut impl Renderer<'s>) { - let mut cursor = self.size().snap(area.center(), Alignment2D::CENTER); + let mut cursor = area.left_center(); let digits = self.digits.len(); let dots_visible = digits.min(MAX_VISIBLE_DOTS); let step = Self::DOT + Self::PADDING; // Jiggle when overflowed. - if digits > dots_visible && digits % 2 == 0 { + if digits > MAX_VISIBLE_DOTS + 1 && (digits + 1) % 2 == 0 { cursor.x += Self::TWITCH } + let mut digit_idx = 0; // Small leftmost dot. - if digits > dots_visible + 1 { - shape::ToifImage::new(cursor - Offset::x(2 * step), theme::DOT_SMALL.toif) - .with_align(Alignment2D::TOP_LEFT) - .with_fg(self.style.text_color) + if digits > MAX_VISIBLE_DOTS + 1 { + shape::ToifImage::new(cursor, theme::DOT_SMALL.toif) + .with_align(Alignment2D::CENTER_LEFT) + .with_fg(theme::GREY) .render(target); + cursor.x += step; + digit_idx += 1; } // Greyed out dot. - if digits > dots_visible { - shape::ToifImage::new(cursor - Offset::x(step), theme::DOT_ACTIVE.toif) - .with_align(Alignment2D::TOP_LEFT) - .with_fg(theme::GREY_LIGHT) + if digits > MAX_VISIBLE_DOTS { + shape::ToifImage::new(cursor, theme::DOT_SMALL.toif) + .with_align(Alignment2D::CENTER_LEFT) + .with_fg(self.style.text_color) .render(target); + cursor.x += step; + digit_idx += 1; } // Draw a dot for each PIN digit. - for _ in 0..dots_visible { - shape::ToifImage::new(cursor, theme::DOT_ACTIVE.toif) - .with_align(Alignment2D::TOP_LEFT) + for _ in digit_idx..dots_visible { + shape::ToifImage::new(cursor, theme::ICON_PIN_BULLET.toif) + .with_align(Alignment2D::CENTER_LEFT) .with_fg(self.style.text_color) .render(target); cursor.x += step; @@ -521,13 +444,7 @@ impl Component for PinDots { } fn paint(&mut self) { - let dot_area = self.area.inset(HEADER_PADDING); - self.pad.paint(); - if self.display_digits { - self.paint_digits(dot_area) - } else { - self.paint_dots(dot_area) - } + // TODO: remove when ui-t3t1 done } fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/word_count.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/word_count.rs index fbda9ca84e..720336473f 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/word_count.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/word_count.rs @@ -25,7 +25,7 @@ impl SelectWordCount { SelectWordCount { button: LABELS.map(|t| { Button::with_text(t.into()) - .styled(theme::button_pin()) + .styled(theme::button_keyboard()) .with_text_align(Alignment::Center) }), } diff --git a/core/embed/rust/src/ui/model_mercury/res/scroll-active.toif b/core/embed/rust/src/ui/model_mercury/res/scroll-active.toif new file mode 100644 index 0000000000..60e142c77f Binary files /dev/null and b/core/embed/rust/src/ui/model_mercury/res/scroll-active.toif differ diff --git a/core/embed/rust/src/ui/model_mercury/res/scroll-inactive-half.toif b/core/embed/rust/src/ui/model_mercury/res/scroll-inactive-half.toif new file mode 100644 index 0000000000..41a181f87b Binary files /dev/null and b/core/embed/rust/src/ui/model_mercury/res/scroll-inactive-half.toif differ diff --git a/core/embed/rust/src/ui/model_mercury/res/scroll-inactive-quarter.toif b/core/embed/rust/src/ui/model_mercury/res/scroll-inactive-quarter.toif new file mode 100644 index 0000000000..379b3b0502 Binary files /dev/null and b/core/embed/rust/src/ui/model_mercury/res/scroll-inactive-quarter.toif differ diff --git a/core/embed/rust/src/ui/model_mercury/res/scroll-inactive.toif b/core/embed/rust/src/ui/model_mercury/res/scroll-inactive.toif new file mode 100644 index 0000000000..a81c1f9369 Binary files /dev/null and b/core/embed/rust/src/ui/model_mercury/res/scroll-inactive.toif differ diff --git a/core/embed/rust/src/ui/model_mercury/res/scroll-small.toif b/core/embed/rust/src/ui/model_mercury/res/scroll-small.toif new file mode 100644 index 0000000000..45cdd20fb0 Binary files /dev/null and b/core/embed/rust/src/ui/model_mercury/res/scroll-small.toif differ diff --git a/core/embed/rust/src/ui/model_mercury/theme/mod.rs b/core/embed/rust/src/ui/model_mercury/theme/mod.rs index 0cb5244d1f..38ae732175 100644 --- a/core/embed/rust/src/ui/model_mercury/theme/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/theme/mod.rs @@ -35,9 +35,11 @@ pub const GREY_DARK: Color = Color::rgb(0x46, 0x48, 0x4A); pub const GREY: Color = Color::rgb(0x8B, 0x8F, 0x93); // secondary text, subtitle, instructions pub const GREY_LIGHT: Color = Color::rgb(0xC7, 0xCD, 0xD3); // content pub const GREY_EXTRA_LIGHT: Color = Color::rgb(0xF0, 0xF0, 0xF0); // primary text, header +pub const GREEN_DARK: Color = Color::rgb(0x06, 0x1E, 0x19); pub const GREEN: Color = Color::rgb(0x08, 0x74, 0x48); pub const GREEN_LIGHT: Color = Color::rgb(0x0B, 0xA5, 0x67); pub const GREEN_LIME: Color = Color::rgb(0x9B, 0xE8, 0x87); +pub const ORANGE_DARK: Color = Color::rgb(0x18, 0x0C, 0x0A); pub const ORANGE_DIMMED: Color = Color::rgb(0x9E, 0x57, 0x42); pub const ORANGE_LIGHT: Color = Color::rgb(0xFF, 0x8D, 0x6A); // cancel button @@ -46,7 +48,6 @@ pub const RED: Color = Color::rgb(0xE7, 0x0E, 0x0E); // button pub const RED_DARK: Color = Color::rgb(0xAE, 0x09, 0x09); // button pressed pub const YELLOW: Color = Color::rgb(0xD9, 0x9E, 0x00); // button pub const YELLOW_DARK: Color = Color::rgb(0x7A, 0x58, 0x00); // button pressed -pub const GREEN_DARK: Color = Color::rgb(0x00, 0x55, 0x1D); // button pressed pub const BLUE: Color = Color::rgb(0x06, 0x1E, 0xAD); // button pub const BLUE_DARK: Color = Color::rgb(0x04, 0x10, 0x58); // button pressed pub const OFF_WHITE: Color = Color::rgb(0xDE, 0xDE, 0xDE); // very light grey @@ -109,6 +110,20 @@ include_icon!( ); include_icon!(ICON_CENTRAL_CIRCLE, "model_mercury/res/central_circle.toif"); +// Scrollbar/PIN dots - taken from model T +include_icon!(DOT_ACTIVE, "model_mercury/res/scroll-active.toif"); +include_icon!(DOT_INACTIVE, "model_mercury/res/scroll-inactive.toif"); +include_icon!( + DOT_INACTIVE_HALF, + "model_mercury/res/scroll-inactive-half.toif" +); +include_icon!( + DOT_INACTIVE_QUARTER, + "model_mercury/res/scroll-inactive-quarter.toif" +); +include_icon!(DOT_SMALL, "model_mercury/res/scroll-small.toif"); +include_icon!(ICON_PIN_BULLET, "model_mercury/res/pin_bullet6.toif"); + // TODO remove TT icons: // Button icons. @@ -163,16 +178,6 @@ include_icon!(ICON_LOGO_EMPTY, "model_tt/res/lock_empty.toif"); // Default homescreen pub const IMAGE_HOMESCREEN: &[u8] = include_res!("model_tt/res/bg.jpg"); -// Scrollbar/PIN dots. -include_icon!(DOT_ACTIVE, "model_tt/res/scroll-active.toif"); -include_icon!(DOT_INACTIVE, "model_tt/res/scroll-inactive.toif"); -include_icon!(DOT_INACTIVE_HALF, "model_tt/res/scroll-inactive-half.toif"); -include_icon!( - DOT_INACTIVE_QUARTER, - "model_tt/res/scroll-inactive-quarter.toif" -); -include_icon!(DOT_SMALL, "model_tt/res/scroll-small.toif"); - pub const fn label_default() -> TextStyle { TEXT_NORMAL } @@ -399,6 +404,7 @@ pub const fn button_danger() -> ButtonStyleSheet { } } +// TODO: delete pub const fn button_reset() -> ButtonStyleSheet { ButtonStyleSheet { normal: &ButtonStyle { @@ -425,7 +431,8 @@ pub const fn button_reset() -> ButtonStyleSheet { } } -pub const fn button_pin() -> ButtonStyleSheet { +// used for PIN digit keys and passphrase/recovery letter keys +pub const fn button_keyboard() -> ButtonStyleSheet { ButtonStyleSheet { normal: &ButtonStyle { font: Font::NORMAL, @@ -436,9 +443,9 @@ pub const fn button_pin() -> ButtonStyleSheet { }, active: &ButtonStyle { font: Font::NORMAL, - text_color: GREY_LIGHT, - button_color: GREY_EXTRA_DARK, - icon_color: GREY_LIGHT, + text_color: BG, + button_color: GREY_LIGHT, + icon_color: BG, background_color: BG, }, disabled: &ButtonStyle { @@ -451,24 +458,49 @@ pub const fn button_pin() -> ButtonStyleSheet { } } -// TODO: will button_pin_xyz styles be the same for PIN and Mnemonic keyboard? -// Wait for Figma pub const fn button_pin_confirm() -> ButtonStyleSheet { ButtonStyleSheet { normal: &ButtonStyle { font: Font::MONO, text_color: FG, - button_color: BG, - icon_color: GREEN_LIGHT, + button_color: GREEN_DARK, + icon_color: GREEN_LIME, background_color: BG, }, active: &ButtonStyle { font: Font::MONO, text_color: FG, - button_color: GREY_DARK, - icon_color: GREEN_LIGHT, + button_color: GREEN_LIGHT, + icon_color: GREEN_DARK, background_color: BG, }, + disabled: &ButtonStyle { + font: Font::MONO, + text_color: GREY_DARK, + button_color: BG, + icon_color: GREY_DARK, + background_color: BG, + }, + } +} + +pub const fn button_pin_cancel() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: BG, // TODO: gradient + icon_color: ORANGE_LIGHT, + background_color: BG, + }, + active: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: ORANGE_LIGHT, + icon_color: BG, + background_color: BG, + }, + // not used disabled: &ButtonStyle { font: Font::MONO, text_color: FG, @@ -479,7 +511,34 @@ pub const fn button_pin_confirm() -> ButtonStyleSheet { } } -pub const fn button_pin_autocomplete() -> ButtonStyleSheet { +pub const fn button_pin_erase() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: BG, // TODO: gradient + icon_color: GREY, + background_color: BG, + }, + active: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: GREY_LIGHT, + icon_color: BG, + background_color: BG, + }, + // not used + disabled: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: BG, + icon_color: GREEN_LIGHT, + background_color: BG, + }, + } +} + +pub const fn button_bip39_autocomplete() -> ButtonStyleSheet { ButtonStyleSheet { normal: &ButtonStyle { font: Font::MONO, @@ -691,7 +750,7 @@ pub const RECOVERY_SPACING: i16 = 18; pub const CORNER_BUTTON_SIDE: i16 = 44; pub const CORNER_BUTTON_SPACING: i16 = BUTTON_SPACING; pub const INFO_BUTTON_HEIGHT: i16 = 44; -pub const PIN_BUTTON_HEIGHT: i16 = 40; +pub const PIN_BUTTON_HEIGHT: i16 = 52; pub const MNEMONIC_BUTTON_HEIGHT: i16 = 52; pub const RESULT_PADDING: i16 = 6; pub const RESULT_FOOTER_START: i16 = 171;