diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index bd7a90819..bbfa9e2bc 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -527,6 +527,7 @@ static void _librust_qstrs(void) { MP_QSTR_show_share_words; MP_QSTR_show_simple; MP_QSTR_show_success; + MP_QSTR_show_tx_context_menu; MP_QSTR_show_wait_text; MP_QSTR_show_warning; MP_QSTR_sign; diff --git a/core/embed/rust/src/ui/model_mercury/component/button.rs b/core/embed/rust/src/ui/model_mercury/component/button.rs index fa77f1909..6b539bc7e 100644 --- a/core/embed/rust/src/ui/model_mercury/component/button.rs +++ b/core/embed/rust/src/ui/model_mercury/component/button.rs @@ -32,10 +32,11 @@ pub struct Button { long_timer: Option, } -impl Button { - /// Offsets the baseline of the button text either up (negative) or down - /// (positive). - pub const BASELINE_OFFSET: i16 = -2; +impl Button { + /// Offsets the baseline of the button text + /// -x/+x => left/right + /// -y/+y => up/down + pub const BASELINE_OFFSET: Offset = Offset::new(2, 6); pub const fn new(content: ButtonContent) -> Self { Self { @@ -57,8 +58,8 @@ impl Button { Self::new(ButtonContent::Icon(icon)) } - pub const fn with_icon_and_text(content: IconText) -> Self { - Self::new(ButtonContent::IconAndText(content)) + pub const fn with_icon_and_text(content: IconText) -> Self { + Self::new(ButtonContent::IconAndText::(content)) } pub const fn with_icon_blend(bg: Icon, fg: Icon, fg_offset: Offset) -> Self { @@ -203,20 +204,15 @@ impl Button { match &self.content { ButtonContent::Empty => {} ButtonContent::Text(text) => { - let width = text.map(|c| style.font.text_width(c)); - let height = style.font.text_height(); - let start_of_baseline = self.area.center() - + Offset::new(-width / 2, height / 2) - + Offset::y(Self::BASELINE_OFFSET); - text.map(|text| { - display::text_left( - start_of_baseline, - text, - style.font, - style.text_color, - style.button_color, - ); - }); + let text = text.as_ref(); + let start_of_baseline = self.area.center() + Self::BASELINE_OFFSET; + display::text_left( + start_of_baseline, + text, + style.font, + style.text_color, + style.button_color, + ); } ButtonContent::Icon(icon) => { icon.draw( @@ -242,17 +238,12 @@ impl Button { match &self.content { ButtonContent::Empty => {} ButtonContent::Text(text) => { - let width = text.map(|c| style.font.text_width(c)); - let height = style.font.text_height(); - let start_of_baseline = self.area.center() - + Offset::new(-width / 2, height / 2) - + Offset::y(Self::BASELINE_OFFSET); - text.map(|text| { - shape::Text::new(start_of_baseline, text) - .with_font(style.font) - .with_fg(style.text_color) - .render(target); - }); + let text = text.as_ref(); + let start_of_baseline = self.area.left_center() + Self::BASELINE_OFFSET; + shape::Text::new(start_of_baseline, text) + .with_font(style.font) + .with_fg(style.text_color) + .render(target); } ButtonContent::Icon(icon) => { shape::ToifImage::new(self.area.center(), icon.toif) @@ -261,7 +252,7 @@ impl Button { .render(target); } ButtonContent::IconAndText(child) => { - child.render(target, self.area, self.style(), Self::BASELINE_OFFSET); + child.render(target, self.area, style, Self::BASELINE_OFFSET); } ButtonContent::IconBlend(bg, fg, offset) => { shape::Bar::new(self.area) @@ -385,7 +376,7 @@ impl crate::trace::Trace for Button { ButtonContent::Text(text) => t.string("text", *text), ButtonContent::Icon(_) => t.bool("icon", true), ButtonContent::IconAndText(content) => { - t.string("text", content.text.into()); + t.string("text", content.text.as_ref().into()); t.bool("icon", true); } ButtonContent::IconBlend(_, _, _) => t.bool("icon", true), @@ -406,7 +397,7 @@ pub enum ButtonContent { Empty, Text(TString<'static>), Icon(Icon), - IconAndText(IconText), + IconAndText(IconText), IconBlend(Icon, Icon, Offset), } @@ -428,6 +419,115 @@ pub struct ButtonStyle { pub border_width: i16, } +impl Button { + pub fn cancel_confirm( + left: Button, + right: Button, + left_is_small: bool, + ) -> CancelConfirm< + T, + impl Fn(ButtonMsg) -> Option, + impl Fn(ButtonMsg) -> Option, + > + where + T: AsRef, + { + let width = if left_is_small { + theme::BUTTON_WIDTH + } else { + 0 + }; + theme::button_bar(Split::left( + width, + theme::BUTTON_SPACING, + left.map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Cancelled) + }), + right.map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) + }), + )) + } + + pub fn cancel_confirm_text( + left: Option, + right: Option, + ) -> CancelConfirm< + T, + impl Fn(ButtonMsg) -> Option, + impl Fn(ButtonMsg) -> Option, + > + where + T: AsRef, + { + let left_is_small: bool; + + let left = if let Some(verb) = left { + left_is_small = verb.as_ref().len() <= 4; + if verb.as_ref() == "^" { + Button::with_icon(theme::ICON_UP) + } else { + Button::with_text(verb) + } + } else { + left_is_small = right.is_some(); + Button::with_icon(theme::ICON_CANCEL) + }; + let right = if let Some(verb) = right { + Button::with_text(verb).styled(theme::button_confirm()) + } else { + Button::with_icon(theme::ICON_CONFIRM).styled(theme::button_confirm()) + }; + Self::cancel_confirm(left, right, left_is_small) + } + + pub fn cancel_info_confirm( + confirm: T, + info: T, + ) -> CancelInfoConfirm< + T, + impl Fn(ButtonMsg) -> Option, + impl Fn(ButtonMsg) -> Option, + impl Fn(ButtonMsg) -> Option, + > + where + T: AsRef, + { + let right = Button::with_text(confirm) + .styled(theme::button_confirm()) + .map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelInfoConfirmMsg::Confirmed) + }); + let top = Button::with_text(info) + .styled(theme::button_moreinfo()) + .map(|msg| (matches!(msg, ButtonMsg::Clicked)).then(|| CancelInfoConfirmMsg::Info)); + let left = Button::with_icon(theme::ICON_CANCEL).map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelInfoConfirmMsg::Cancelled) + }); + let total_height = theme::BUTTON_HEIGHT + theme::BUTTON_SPACING + theme::INFO_BUTTON_HEIGHT; + FixedHeightBar::bottom( + Split::top( + theme::INFO_BUTTON_HEIGHT, + theme::BUTTON_SPACING, + top, + Split::left(theme::BUTTON_WIDTH, theme::BUTTON_SPACING, left, right), + ), + total_height, + ) + } +} + +pub enum CancelConfirmMsg { + Cancelled, + Confirmed, +} + +type CancelInfoConfirm = FixedHeightBar< + Split, F0>, Split, F1>, MsgMap, F2>>>, +>; + +type CancelConfirm = FixedHeightBar, F0>, MsgMap, F1>>>; + #[derive(Clone, Copy)] pub enum CancelInfoConfirmMsg { Cancelled, @@ -436,23 +536,25 @@ pub enum CancelInfoConfirmMsg { } #[derive(PartialEq, Eq)] -pub struct IconText { - text: &'static str, +pub struct IconText { + text: T, icon: Icon, } -impl IconText { +impl IconText +where + T: AsRef, +{ const ICON_SPACE: i16 = 46; const ICON_MARGIN: i16 = 4; const TEXT_MARGIN: i16 = 6; - pub fn new(text: &'static str, icon: Icon) -> Self { + pub fn new(text: T, icon: Icon) -> Self { Self { text, icon } } - pub fn paint(&self, area: Rect, style: &ButtonStyle, baseline_offset: i16) { - let width = style.font.text_width(self.text); - let height = style.font.text_height(); + pub fn paint(&self, area: Rect, style: &ButtonStyle, baseline_offset: Offset) { + let width = style.font.text_width(self.text.as_ref()); let mut use_icon = false; let mut use_text = false; @@ -461,8 +563,7 @@ impl IconText { area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2), area.center().y, ); - let mut text_pos = - area.center() + Offset::new(-width / 2, height / 2) + Offset::y(baseline_offset); + let mut text_pos = area.left_center() + baseline_offset; if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) { //display both icon and text @@ -480,7 +581,7 @@ impl IconText { if use_text { display::text_left( text_pos, - self.text, + self.text.as_ref(), style.font, style.text_color, style.button_color, @@ -496,16 +597,14 @@ impl IconText { ); } } - pub fn render<'s>( - &self, + & self, target: &mut impl Renderer<'s>, area: Rect, style: &ButtonStyle, - baseline_offset: i16, + baseline_offset: Offset, ) { - let width = style.font.text_width(self.text); - let height = style.font.text_height(); + let width = style.font.text_width(self.text.as_ref()); let mut use_icon = false; let mut use_text = false; @@ -514,8 +613,7 @@ impl IconText { area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2), area.center().y, ); - let mut text_pos = - area.center() + Offset::new(-width / 2, height / 2) + Offset::y(baseline_offset); + let mut text_pos = area.left_center() + baseline_offset; if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) { //display both icon and text @@ -531,12 +629,10 @@ impl IconText { } if use_text { - shape::Text::new(text_pos, self.text) - .with_font(style.font) + shape::Text::new(text_pos, self.text.as_ref()) .with_fg(style.text_color) .render(target); } - if use_icon { shape::ToifImage::new(icon_pos, self.icon.toif) .with_align(Alignment2D::CENTER) diff --git a/core/embed/rust/src/ui/model_mercury/component/frame.rs b/core/embed/rust/src/ui/model_mercury/component/frame.rs index 53a2c90ae..7448fd0fd 100644 --- a/core/embed/rust/src/ui/model_mercury/component/frame.rs +++ b/core/embed/rust/src/ui/model_mercury/component/frame.rs @@ -62,8 +62,8 @@ where self } - pub fn with_subtitle(mut self, subtitle: TString<'static>) -> Self { - let style = theme::label_title_sub(); + pub fn with_subtitle(mut self, subtitle: U) -> Self { + let style = theme::TEXT_SUB; self.title = Child::new(self.title.into_inner().top_aligned()); self.subtitle = Some(Child::new(Label::new( subtitle, @@ -127,7 +127,7 @@ where fn place(&mut self, bounds: Rect) -> Rect { let (mut header_area, content_area) = bounds.split_top(TITLE_HEIGHT); - let content_area = content_area.inset(Insets::right(TITLE_SPACE)); + let content_area = content_area.inset(Insets::top(TITLE_SPACE)); if let Some(b) = &mut self.button { let (rest, button_area) = header_area.split_right(TITLE_HEIGHT); diff --git a/core/embed/rust/src/ui/model_mercury/component/homescreen/mod.rs b/core/embed/rust/src/ui/model_mercury/component/homescreen/mod.rs index 13765bba2..1d85c744c 100644 --- a/core/embed/rust/src/ui/model_mercury/component/homescreen/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/component/homescreen/mod.rs @@ -22,7 +22,7 @@ use crate::{ ui::{ constant::HEIGHT, display::{ - tjpgd::{jpeg_test, BufferInput}, + tjpgd::BufferInput, toif::{Toif, ToifFormat}, }, model_mercury::component::homescreen::render::{ diff --git a/core/embed/rust/src/ui/model_mercury/component/mod.rs b/core/embed/rust/src/ui/model_mercury/component/mod.rs index 75d516194..598c01ce5 100644 --- a/core/embed/rust/src/ui/model_mercury/component/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/component/mod.rs @@ -1,5 +1,12 @@ pub mod bl_confirm; mod button; +#[cfg(feature = "translations")] +mod coinjoin_progress; +mod dialog; +mod fido; +mod vertical_menu; +#[rustfmt::skip] +mod fido_icons; mod error; mod frame; mod loader; @@ -13,6 +20,10 @@ pub use error::ErrorScreen; pub use frame::{Frame, FrameMsg}; pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet}; pub use result::{ResultFooter, ResultScreen, ResultStyle}; +pub use scroll::ScrollBar; +pub use simple_page::SimplePage; +pub use swipe::{Swipe, SwipeDirection}; +pub use vertical_menu::{VerticalMenu, VerticalMenuChoiceMsg}; pub use welcome_screen::WelcomeScreen; use super::{constant, theme}; diff --git a/core/embed/rust/src/ui/model_mercury/component/number_input.rs b/core/embed/rust/src/ui/model_mercury/component/number_input.rs index 43aab67cb..891bb8ab2 100644 --- a/core/embed/rust/src/ui/model_mercury/component/number_input.rs +++ b/core/embed/rust/src/ui/model_mercury/component/number_input.rs @@ -226,7 +226,7 @@ impl Component for NumberInput { let mut buf = [0u8; 10]; if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) { let digit_font = Font::DEMIBOLD; - let y_offset = digit_font.text_height() / 2 + Button::<&str>::BASELINE_OFFSET; + let y_offset = digit_font.text_height() / 2 + Button::<&str>::BASELINE_OFFSET.y; display::rect_fill(self.area, theme::BG); display::text_center( self.area.center() + Offset::y(y_offset), @@ -245,7 +245,7 @@ impl Component for NumberInput { if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) { let digit_font = Font::DEMIBOLD; - let y_offset = digit_font.text_height() / 2 + Button::<&str>::BASELINE_OFFSET; + let y_offset = digit_font.text_height() / 2 + Button::<&str>::BASELINE_OFFSET.y; shape::Bar::new(self.area).with_bg(theme::BG).render(target); shape::Text::new(self.area.center() + Offset::y(y_offset), text) diff --git a/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs b/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs new file mode 100644 index 000000000..575819b70 --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs @@ -0,0 +1,156 @@ +use heapless::Vec; + +use super::theme; +use crate::ui::{ + component::{base::Component, Event, EventCtx}, + display::Icon, + geometry::Rect, + model_mercury::component::button::{Button, ButtonMsg, IconText}, + shape::{Bar, Renderer}, +}; + +pub enum VerticalMenuChoiceMsg { + Selected(usize), +} + +/// Number of buttons. +/// Presently, VerticalMenu holds only fixed number of buttons. +/// TODO: for scrollable menu, the implementation must change. +const N_ITEMS: usize = 3; + +/// Number of visual separators between buttons. +const N_SEPS: usize = N_ITEMS - 1; + +/// Fixed height of each menu button. +const MENU_BUTTON_HEIGHT: i16 = 64; + +/// Fixed height of a separator. +const MENU_SEP_HEIGHT: i16 = 2; + +type VerticalMenuButtons = Vec, N_ITEMS>; +type AreasForSeparators = Vec; + +pub struct VerticalMenu { + area: Rect, + /// buttons placed vertically from top to bottom + buttons: VerticalMenuButtons, + /// areas for visual separators between buttons + areas_sep: AreasForSeparators, +} + +impl VerticalMenu +where + T: AsRef, +{ + fn new(buttons: VerticalMenuButtons) -> Self { + Self { + area: Rect::zero(), + buttons, + areas_sep: AreasForSeparators::new(), + } + } + pub fn select_word(words: [T; 3]) -> Self { + let mut buttons_vec = VerticalMenuButtons::new(); + for word in words { + let button = Button::with_text(word).styled(theme::button_vertical_menu()); + unwrap!(buttons_vec.push(button)); + } + Self::new(buttons_vec) + } + + pub fn context_menu(options: [(T, Icon); 3]) -> Self { + // TODO: this is just POC + let mut buttons_vec = VerticalMenuButtons::new(); + for opt in options { + let button_theme; + match opt.1 { + theme::ICON_CANCEL => { + button_theme = theme::button_vertical_menu_orange(); + } + _ => { + button_theme = theme::button_vertical_menu(); + } + } + unwrap!(buttons_vec.push( + Button::with_icon_and_text(IconText::new(opt.0, opt.1)).styled(button_theme) + )); + } + Self::new(buttons_vec) + } +} + +impl Component for VerticalMenu +where + T: AsRef, +{ + type Msg = VerticalMenuChoiceMsg; + + fn place(&mut self, bounds: Rect) -> Rect { + // VerticalMenu is supposed to be used in Frame, the remaining space is just + // enought to fit 3 buttons separated by thin bars + let height_bounds_expected = 3 * MENU_BUTTON_HEIGHT + 2 * MENU_SEP_HEIGHT; + assert!(bounds.height() == height_bounds_expected); + + self.area = bounds; + self.areas_sep.clear(); + let mut remaining = bounds; + for i in 0..N_ITEMS { + let (area_button, new_remaining) = remaining.split_top(MENU_BUTTON_HEIGHT); + self.buttons[i].place(area_button); + remaining = new_remaining; + if i < N_SEPS { + let (area_sep, new_remaining) = remaining.split_top(MENU_SEP_HEIGHT); + unwrap!(self.areas_sep.push(area_sep)); + remaining = new_remaining; + } + } + + self.area + } + + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { + for (i, button) in self.buttons.iter_mut().enumerate() { + if let Some(ButtonMsg::Clicked) = button.event(ctx, event) { + return Some(VerticalMenuChoiceMsg::Selected(i)); + } + } + None + } + + fn paint(&mut self) { + // TODO remove when ui-t3t1 done + } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // render buttons separated by thin bars + for button in &self.buttons { + button.render(target); + } + for area in self.areas_sep.iter() { + Bar::new(*area) + .with_thickness(MENU_SEP_HEIGHT) + .with_fg(theme::GREY_EXTRA_DARK) + .render(target); + } + } + + #[cfg(feature = "ui_bounds")] + fn bounds(&self, sink: &mut dyn FnMut(Rect)) { + sink(self.area); + } +} + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for VerticalMenu +where + T: AsRef, +{ + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.component("VerticalMenu"); + t.in_list("buttons", &|button_list| { + for button in &self.buttons { + button_list.child(button); + } + }); + } +} diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index a2733bb83..d217ca171 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -34,7 +34,7 @@ use crate::{ }, Border, Component, Empty, FormattedText, Label, Never, Qr, Timeout, }, - display::tjpgd::jpeg_info, + display::{tjpgd::jpeg_info, Icon}, geometry, layout::{ obj::{ComponentMsgObj, LayoutObj}, @@ -52,7 +52,8 @@ use super::{ FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg, PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, - SelectWordCount, SelectWordCountMsg, SelectWordMsg, SimplePage, Slip39Input, + SelectWordCount, SelectWordCountMsg, SelectWordMsg, SimplePage, Slip39Input, VerticalMenu, + VerticalMenuChoiceMsg, }, theme, }; @@ -100,6 +101,16 @@ impl TryFrom for Obj { } } +impl TryFrom for Obj { + type Error = Error; + + fn try_from(value: VerticalMenuChoiceMsg) -> Result { + match value { + VerticalMenuChoiceMsg::Selected(i) => i.try_into(), + } + } +} + impl ComponentMsgObj for FidoConfirm where F: Fn(usize) -> T, @@ -195,6 +206,17 @@ where } } +impl ComponentMsgObj for VerticalMenu +where + T: AsRef, +{ + fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { + match msg { + VerticalMenuChoiceMsg::Selected(i) => i.try_into(), + } + } +} + impl ComponentMsgObj for ButtonPage where T: Component + Paginate, @@ -1299,11 +1321,30 @@ extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?; let words: [StrBuffer; 3] = util::iter_into_array(words_iterable)?; - let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_DEMIBOLD, description)]); - let obj = LayoutObj::new(Frame::left_aligned( - title, - Dialog::new(paragraphs, Button::select_word(words)), - ))?; + let content = VerticalMenu::select_word(words); + let frame_with_menu = Frame::left_aligned(title, content).with_subtitle(description); + let obj = LayoutObj::new(frame_with_menu)?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_tx_context_menu(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], _kwargs: &Map| { + // TODO: this is just POC + let title: StrBuffer = StrBuffer::from(""); + + let options: [(StrBuffer, Icon); 3] = [ + (StrBuffer::from("Address QR code"), theme::ICON_QR_CODE), + ( + StrBuffer::from("Fee info"), + theme::ICON_CHEVRON_RIGHT, + ), + (StrBuffer::from("Cancel transaction"), theme::ICON_CANCEL), + ]; + let content = VerticalMenu::context_menu(options); + let frame_with_menu = Frame::left_aligned(title, content).with_cancel_button(); + let obj = LayoutObj::new(frame_with_menu)?; Ok(obj.into()) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } @@ -2033,6 +2074,11 @@ pub static mp_module_trezorui2: Module = obj_module! { /// iterable must be of exact size. Returns index in range `0..3`.""" Qstr::MP_QSTR_select_word => obj_fn_kw!(0, new_select_word).as_obj(), + /// def show_tx_context_menu() -> LayoutObj[int]: + /// """Show transaction context menu with the options for 1) Address QR code, 2) Fee + /// information, 3) Cancel transaction""" + Qstr::MP_QSTR_show_tx_context_menu => obj_fn_kw!(0, new_show_tx_context_menu).as_obj(), + /// def show_share_words( /// *, /// title: str, diff --git a/core/embed/rust/src/ui/model_mercury/res/chevron_right24.png b/core/embed/rust/src/ui/model_mercury/res/chevron_right24.png index 5dea415ff..efe01e116 100644 Binary files a/core/embed/rust/src/ui/model_mercury/res/chevron_right24.png and b/core/embed/rust/src/ui/model_mercury/res/chevron_right24.png differ diff --git a/core/embed/rust/src/ui/model_mercury/res/chevron_right24.toif b/core/embed/rust/src/ui/model_mercury/res/chevron_right24.toif index 32666c3ec..8333079d6 100644 Binary files a/core/embed/rust/src/ui/model_mercury/res/chevron_right24.toif and b/core/embed/rust/src/ui/model_mercury/res/chevron_right24.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 fd6e556ff..1b0f116cb 100644 --- a/core/embed/rust/src/ui/model_mercury/theme/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/theme/mod.rs @@ -44,7 +44,7 @@ pub const FATAL_ERROR_COLOR: Color = Color::rgb(0xE7, 0x0E, 0x0E); pub const FATAL_ERROR_HIGHLIGHT_COLOR: Color = Color::rgb(0xFF, 0x41, 0x41); // Commonly used corner radius (i.e. for buttons). -pub const RADIUS: u8 = 2; +pub const RADIUS: u8 = 0; // UI icons (greyscale). @@ -179,6 +179,298 @@ pub const fn button_moreinfo() -> ButtonStyleSheet { } } +pub const fn button_info() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::BOLD, + text_color: FG, + button_color: BLUE, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + active: &ButtonStyle { + font: Font::BOLD, + text_color: FG, + button_color: BLUE_DARK, + background_color: BG, + border_color: FG, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: Font::BOLD, + text_color: GREY_LIGHT, + button_color: BLUE, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + } +} + +pub const fn button_vertical_menu() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::NORMAL, + text_color: GREY_EXTRA_LIGHT, + button_color: BG, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + // TODO: change when figma done + active: &ButtonStyle { + font: Font::NORMAL, + text_color: FG, + button_color: GREEN_LIME, + background_color: GREY_EXTRA_DARK, + border_color: GREEN_LIME, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: Font::NORMAL, + text_color: GREY_LIGHT, + button_color: GREEN_LIME, + background_color: GREY_EXTRA_DARK, + border_color: GREEN_LIME, + border_radius: RADIUS, + border_width: 0, + }, + } +} + +pub const fn button_vertical_menu_orange() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::NORMAL, + text_color: ORANGE_LIGHT, + button_color: BG, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + active: &ButtonStyle { + font: Font::NORMAL, + text_color: FG, + button_color: GREEN_LIME, + background_color: GREY_EXTRA_DARK, + border_color: GREEN_LIME, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: Font::NORMAL, + text_color: GREY_LIGHT, + button_color: GREEN_LIME, + background_color: GREY_EXTRA_DARK, + border_color: GREEN_LIME, + border_radius: RADIUS, + border_width: 0, + }, + } +} +pub const fn button_pin() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: GREY_DARK, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + active: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: GREY_MEDIUM, + background_color: BG, + border_color: FG, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: Font::MONO, + text_color: GREY_LIGHT, + button_color: BG, // so there is no "button" itself, just the text + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + } +} + +pub const fn button_pin_confirm() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: GREEN, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + active: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: GREEN_DARK, + background_color: BG, + border_color: FG, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: Font::MONO, + text_color: GREY_LIGHT, + button_color: GREY_DARK, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + } +} + +pub const fn button_pin_autocomplete() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: GREY_DARK, // same as PIN buttons + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + active: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: GREEN_DARK, + background_color: BG, + border_color: FG, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: Font::MONO, + text_color: GREY_LIGHT, + button_color: BG, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + } +} + +pub const fn button_suggestion_confirm() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::MONO, + text_color: GREEN_DARK, + button_color: GREEN, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + active: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: GREEN_DARK, + background_color: BG, + border_color: FG, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: Font::MONO, + text_color: GREY_LIGHT, + button_color: GREY_DARK, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + } +} + +pub const fn button_suggestion_autocomplete() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::MONO, + text_color: GREY_LIGHT, + button_color: GREY_DARK, // same as PIN buttons + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + active: &ButtonStyle { + font: Font::MONO, + text_color: FG, + button_color: GREEN_DARK, + background_color: BG, + border_color: FG, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: Font::MONO, + text_color: GREY_LIGHT, + button_color: BG, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + } +} + +pub const fn button_counter() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::DEMIBOLD, + text_color: FG, + button_color: GREY_DARK, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + active: &ButtonStyle { + font: Font::DEMIBOLD, + text_color: FG, + button_color: GREY_MEDIUM, + background_color: BG, + border_color: FG, + border_radius: RADIUS, + border_width: 0, + }, + disabled: &ButtonStyle { + font: Font::DEMIBOLD, + text_color: GREY_LIGHT, + button_color: GREY_DARK, + background_color: BG, + border_color: BG, + border_radius: RADIUS, + border_width: 0, + }, + } +} + +pub const fn button_clear() -> ButtonStyleSheet { + button_default() +} + pub const fn loader_default() -> LoaderStyleSheet { LoaderStyleSheet { normal: &LoaderStyle { diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 985de7908..696ff828e 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -389,6 +389,12 @@ def select_word( iterable must be of exact size. Returns index in range `0..3`.""" +# rust/src/ui/model_mercury/layout.rs +def show_tx_context_menu() -> LayoutObj[int]: + """Show transaction context menu with the options for 1) Address QR code, 2) Fee + information, 3) Cancel transaction""" + + # rust/src/ui/model_mercury/layout.rs def show_share_words( *,