mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 20:38:10 +00:00
feat(core): T3T1 vertical menu
This commit is contained in:
parent
55067a6d40
commit
80462282dc
@ -32,10 +32,11 @@ pub struct Button {
|
||||
long_timer: Option<TimerToken>,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
/// Offsets the baseline of the button text either up (negative) or down
|
||||
/// (positive).
|
||||
pub const BASELINE_OFFSET: i16 = -2;
|
||||
impl<T> Button<T> {
|
||||
/// 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<T>) -> Self {
|
||||
Self::new(ButtonContent::IconAndText::<T>(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<T>),
|
||||
IconBlend(Icon, Icon, Offset),
|
||||
}
|
||||
|
||||
@ -428,6 +419,115 @@ pub struct ButtonStyle {
|
||||
pub border_width: i16,
|
||||
}
|
||||
|
||||
impl<T> Button<T> {
|
||||
pub fn cancel_confirm(
|
||||
left: Button<T>,
|
||||
right: Button<T>,
|
||||
left_is_small: bool,
|
||||
) -> CancelConfirm<
|
||||
T,
|
||||
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||
>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
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<T>,
|
||||
right: Option<T>,
|
||||
) -> CancelConfirm<
|
||||
T,
|
||||
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||
>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
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<CancelInfoConfirmMsg>,
|
||||
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
|
||||
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
|
||||
>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
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<T, F0, F1, F2> = FixedHeightBar<
|
||||
Split<MsgMap<Button<T>, F0>, Split<MsgMap<Button<T>, F1>, MsgMap<Button<T>, F2>>>,
|
||||
>;
|
||||
|
||||
type CancelConfirm<T, F0, F1> = FixedHeightBar<Split<MsgMap<Button<T>, F0>, MsgMap<Button<T>, 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<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;
|
||||
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
||||
ui::{
|
||||
constant::HEIGHT,
|
||||
display::{
|
||||
tjpgd::{jpeg_test, BufferInput},
|
||||
tjpgd::BufferInput,
|
||||
toif::{Toif, ToifFormat},
|
||||
},
|
||||
model_mercury::component::homescreen::render::{
|
||||
|
@ -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};
|
||||
|
@ -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.
@ -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 {
|
||||
|
@ -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