mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-03 21:32:33 +00:00
feat(core): T3T1 vertical menu
This commit is contained in:
parent
4708572802
commit
25b0e3b048
@ -528,6 +528,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;
|
||||
|
@ -34,9 +34,10 @@ pub struct Button<T> {
|
||||
}
|
||||
|
||||
impl<T> Button<T> {
|
||||
/// Offsets the baseline of the button text either up (negative) or down
|
||||
/// (positive).
|
||||
pub const BASELINE_OFFSET: i16 = -2;
|
||||
/// 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<T>) -> Self {
|
||||
Self {
|
||||
@ -58,8 +59,8 @@ impl<T> Button<T> {
|
||||
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<T>) -> Self {
|
||||
Self::new(ButtonContent::IconAndText::<T>(content))
|
||||
}
|
||||
|
||||
pub const fn with_icon_blend(bg: Icon, fg: Icon, fg_offset: Offset) -> Self {
|
||||
@ -211,11 +212,7 @@ impl<T> Button<T> {
|
||||
ButtonContent::Empty => {}
|
||||
ButtonContent::Text(text) => {
|
||||
let text = text.as_ref();
|
||||
let width = style.font.text_width(text);
|
||||
let height = style.font.text_height();
|
||||
let start_of_baseline = self.area.center()
|
||||
+ Offset::new(-width / 2, height / 2)
|
||||
+ Offset::y(Self::BASELINE_OFFSET);
|
||||
let start_of_baseline = self.area.center() + Self::BASELINE_OFFSET;
|
||||
display::text_left(
|
||||
start_of_baseline,
|
||||
text,
|
||||
@ -252,11 +249,7 @@ impl<T> Button<T> {
|
||||
ButtonContent::Empty => {}
|
||||
ButtonContent::Text(text) => {
|
||||
let text = text.as_ref();
|
||||
let width = style.font.text_width(text);
|
||||
let height = style.font.text_height();
|
||||
let start_of_baseline = self.area.center()
|
||||
+ Offset::new(-width / 2, height / 2)
|
||||
+ Offset::y(Self::BASELINE_OFFSET);
|
||||
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)
|
||||
@ -269,7 +262,7 @@ impl<T> Button<T> {
|
||||
.render(target);
|
||||
}
|
||||
ButtonContent::IconAndText(child) => {
|
||||
child.paint(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)
|
||||
@ -399,7 +392,7 @@ where
|
||||
ButtonContent::Text(text) => t.string("text", text.as_ref().into()),
|
||||
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),
|
||||
@ -420,7 +413,7 @@ pub enum ButtonContent<T> {
|
||||
Empty,
|
||||
Text(T),
|
||||
Icon(Icon),
|
||||
IconAndText(IconText),
|
||||
IconAndText(IconText<T>),
|
||||
IconBlend(Icon, Icon, Offset),
|
||||
}
|
||||
|
||||
@ -538,43 +531,6 @@ impl<T> Button<T> {
|
||||
total_height,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn select_word(
|
||||
words: [T; 3],
|
||||
) -> CancelInfoConfirm<
|
||||
T,
|
||||
impl Fn(ButtonMsg) -> Option<SelectWordMsg>,
|
||||
impl Fn(ButtonMsg) -> Option<SelectWordMsg>,
|
||||
impl Fn(ButtonMsg) -> Option<SelectWordMsg>,
|
||||
>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
let btn = move |i, word| {
|
||||
Button::with_text(word)
|
||||
.styled(theme::button_pin())
|
||||
.map(move |msg| {
|
||||
(matches!(msg, ButtonMsg::Clicked)).then(|| SelectWordMsg::Selected(i))
|
||||
})
|
||||
};
|
||||
|
||||
let [top, middle, bottom] = words;
|
||||
let total_height = 3 * theme::BUTTON_HEIGHT + 2 * theme::BUTTON_SPACING;
|
||||
FixedHeightBar::bottom(
|
||||
Split::top(
|
||||
theme::BUTTON_HEIGHT,
|
||||
theme::BUTTON_SPACING,
|
||||
btn(0, top),
|
||||
Split::top(
|
||||
theme::BUTTON_HEIGHT,
|
||||
theme::BUTTON_SPACING,
|
||||
btn(1, middle),
|
||||
btn(2, bottom),
|
||||
),
|
||||
),
|
||||
total_height,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum CancelConfirmMsg {
|
||||
@ -600,23 +556,25 @@ pub enum SelectWordMsg {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct IconText {
|
||||
text: &'static str,
|
||||
pub struct IconText<T> {
|
||||
text: T,
|
||||
icon: Icon,
|
||||
}
|
||||
|
||||
impl IconText {
|
||||
impl<T> IconText<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
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;
|
||||
@ -625,8 +583,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
|
||||
@ -644,7 +601,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,
|
||||
@ -660,4 +617,47 @@ impl IconText {
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn render<'s>(
|
||||
& self,
|
||||
target: &mut impl Renderer<'s>,
|
||||
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;
|
||||
|
||||
let mut icon_pos = Point::new(
|
||||
area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2),
|
||||
area.center().y,
|
||||
);
|
||||
let mut text_pos = area.left_center() + baseline_offset;
|
||||
|
||||
if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) {
|
||||
//display both icon and text
|
||||
text_pos = Point::new(area.top_left().x + Self::ICON_SPACE, text_pos.y);
|
||||
use_text = true;
|
||||
use_icon = true;
|
||||
} else if area.width() > (width + Self::TEXT_MARGIN) {
|
||||
use_text = true;
|
||||
} else {
|
||||
//if we can't fit the text, retreat to centering the icon
|
||||
icon_pos = area.center();
|
||||
use_icon = true;
|
||||
}
|
||||
|
||||
if use_text {
|
||||
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)
|
||||
.with_fg(style.text_color)
|
||||
.render(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ where
|
||||
}
|
||||
|
||||
pub fn with_subtitle(mut self, subtitle: U) -> Self {
|
||||
let style = theme::label_title_sub();
|
||||
let style = theme::TEXT_SUB;
|
||||
self.title = Child::new(self.title.into_inner().top_aligned());
|
||||
self.subtitle = Some(Child::new(Label::new(
|
||||
subtitle,
|
||||
@ -126,7 +126,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);
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
||||
ui::{
|
||||
constant::HEIGHT,
|
||||
display::{
|
||||
tjpgd::{jpeg_test, BufferInput},
|
||||
tjpgd::BufferInput,
|
||||
toif::{Toif, ToifFormat},
|
||||
},
|
||||
model_mercury::component::homescreen::render::{
|
||||
|
@ -6,6 +6,7 @@ mod button;
|
||||
mod coinjoin_progress;
|
||||
mod dialog;
|
||||
mod fido;
|
||||
mod vertical_menu;
|
||||
#[rustfmt::skip]
|
||||
mod fido_icons;
|
||||
mod error;
|
||||
@ -57,6 +58,7 @@ 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::theme;
|
||||
|
@ -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)
|
||||
|
156
core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs
Normal file
156
core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs
Normal file
@ -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<T> = Vec<Button<T>, N_ITEMS>;
|
||||
type AreasForSeparators = Vec<Rect, N_SEPS>;
|
||||
|
||||
pub struct VerticalMenu<T> {
|
||||
area: Rect,
|
||||
/// buttons placed vertically from top to bottom
|
||||
buttons: VerticalMenuButtons<T>,
|
||||
/// areas for visual separators between buttons
|
||||
areas_sep: AreasForSeparators,
|
||||
}
|
||||
|
||||
impl<T> VerticalMenu<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn new(buttons: VerticalMenuButtons<T>) -> 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<T> Component for VerticalMenu<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
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<Self::Msg> {
|
||||
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<T> crate::trace::Trace for VerticalMenu<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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<SelectWordCountMsg> for Obj {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<VerticalMenuChoiceMsg> for Obj {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VerticalMenuChoiceMsg) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
VerticalMenuChoiceMsg::Selected(i) => i.try_into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, U> ComponentMsgObj for FidoConfirm<F, T, U>
|
||||
where
|
||||
F: Fn(usize) -> T,
|
||||
@ -195,6 +206,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ComponentMsgObj for VerticalMenu<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||
match msg {
|
||||
VerticalMenuChoiceMsg::Selected(i) => i.try_into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ComponentMsgObj for ButtonPage<T, U>
|
||||
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,
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 320 B After Width: | Height: | Size: 226 B |
Binary file not shown.
@ -57,7 +57,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;
|
||||
|
||||
// Full-size QR code.
|
||||
pub const QR_SIDE_MAX: u32 = 140;
|
||||
@ -416,6 +416,70 @@ pub const fn button_info() -> ButtonStyleSheet {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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(
|
||||
*,
|
||||
|
Loading…
Reference in New Issue
Block a user