mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-24 07:18:09 +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>,
|
long_timer: Option<TimerToken>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Button {
|
impl<T> Button<T> {
|
||||||
/// Offsets the baseline of the button text either up (negative) or down
|
/// Offsets the baseline of the button text
|
||||||
/// (positive).
|
/// -x/+x => left/right
|
||||||
pub const BASELINE_OFFSET: i16 = -2;
|
/// -y/+y => up/down
|
||||||
|
pub const BASELINE_OFFSET: Offset = Offset::new(2, 6);
|
||||||
|
|
||||||
pub const fn new(content: ButtonContent) -> Self {
|
pub const fn new(content: ButtonContent) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -57,8 +58,8 @@ impl Button {
|
|||||||
Self::new(ButtonContent::Icon(icon))
|
Self::new(ButtonContent::Icon(icon))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn with_icon_and_text(content: IconText) -> Self {
|
pub const fn with_icon_and_text(content: IconText<T>) -> Self {
|
||||||
Self::new(ButtonContent::IconAndText(content))
|
Self::new(ButtonContent::IconAndText::<T>(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn with_icon_blend(bg: Icon, fg: Icon, fg_offset: Offset) -> Self {
|
pub const fn with_icon_blend(bg: Icon, fg: Icon, fg_offset: Offset) -> Self {
|
||||||
@ -203,12 +204,8 @@ impl Button {
|
|||||||
match &self.content {
|
match &self.content {
|
||||||
ButtonContent::Empty => {}
|
ButtonContent::Empty => {}
|
||||||
ButtonContent::Text(text) => {
|
ButtonContent::Text(text) => {
|
||||||
let width = text.map(|c| style.font.text_width(c));
|
let text = text.as_ref();
|
||||||
let height = style.font.text_height();
|
let start_of_baseline = self.area.center() + Self::BASELINE_OFFSET;
|
||||||
let start_of_baseline = self.area.center()
|
|
||||||
+ Offset::new(-width / 2, height / 2)
|
|
||||||
+ Offset::y(Self::BASELINE_OFFSET);
|
|
||||||
text.map(|text| {
|
|
||||||
display::text_left(
|
display::text_left(
|
||||||
start_of_baseline,
|
start_of_baseline,
|
||||||
text,
|
text,
|
||||||
@ -216,7 +213,6 @@ impl Button {
|
|||||||
style.text_color,
|
style.text_color,
|
||||||
style.button_color,
|
style.button_color,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
ButtonContent::Icon(icon) => {
|
ButtonContent::Icon(icon) => {
|
||||||
icon.draw(
|
icon.draw(
|
||||||
@ -242,17 +238,12 @@ impl Button {
|
|||||||
match &self.content {
|
match &self.content {
|
||||||
ButtonContent::Empty => {}
|
ButtonContent::Empty => {}
|
||||||
ButtonContent::Text(text) => {
|
ButtonContent::Text(text) => {
|
||||||
let width = text.map(|c| style.font.text_width(c));
|
let text = text.as_ref();
|
||||||
let height = style.font.text_height();
|
let start_of_baseline = self.area.left_center() + Self::BASELINE_OFFSET;
|
||||||
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)
|
shape::Text::new(start_of_baseline, text)
|
||||||
.with_font(style.font)
|
.with_font(style.font)
|
||||||
.with_fg(style.text_color)
|
.with_fg(style.text_color)
|
||||||
.render(target);
|
.render(target);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
ButtonContent::Icon(icon) => {
|
ButtonContent::Icon(icon) => {
|
||||||
shape::ToifImage::new(self.area.center(), icon.toif)
|
shape::ToifImage::new(self.area.center(), icon.toif)
|
||||||
@ -261,7 +252,7 @@ impl Button {
|
|||||||
.render(target);
|
.render(target);
|
||||||
}
|
}
|
||||||
ButtonContent::IconAndText(child) => {
|
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) => {
|
ButtonContent::IconBlend(bg, fg, offset) => {
|
||||||
shape::Bar::new(self.area)
|
shape::Bar::new(self.area)
|
||||||
@ -385,7 +376,7 @@ impl crate::trace::Trace for Button {
|
|||||||
ButtonContent::Text(text) => t.string("text", *text),
|
ButtonContent::Text(text) => t.string("text", *text),
|
||||||
ButtonContent::Icon(_) => t.bool("icon", true),
|
ButtonContent::Icon(_) => t.bool("icon", true),
|
||||||
ButtonContent::IconAndText(content) => {
|
ButtonContent::IconAndText(content) => {
|
||||||
t.string("text", content.text.into());
|
t.string("text", content.text.as_ref().into());
|
||||||
t.bool("icon", true);
|
t.bool("icon", true);
|
||||||
}
|
}
|
||||||
ButtonContent::IconBlend(_, _, _) => t.bool("icon", true),
|
ButtonContent::IconBlend(_, _, _) => t.bool("icon", true),
|
||||||
@ -406,7 +397,7 @@ pub enum ButtonContent {
|
|||||||
Empty,
|
Empty,
|
||||||
Text(TString<'static>),
|
Text(TString<'static>),
|
||||||
Icon(Icon),
|
Icon(Icon),
|
||||||
IconAndText(IconText),
|
IconAndText(IconText<T>),
|
||||||
IconBlend(Icon, Icon, Offset),
|
IconBlend(Icon, Icon, Offset),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,6 +419,115 @@ pub struct ButtonStyle {
|
|||||||
pub border_width: i16,
|
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)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum CancelInfoConfirmMsg {
|
pub enum CancelInfoConfirmMsg {
|
||||||
Cancelled,
|
Cancelled,
|
||||||
@ -436,23 +536,25 @@ pub enum CancelInfoConfirmMsg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub struct IconText {
|
pub struct IconText<T> {
|
||||||
text: &'static str,
|
text: T,
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IconText {
|
impl<T> IconText<T>
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
const ICON_SPACE: i16 = 46;
|
const ICON_SPACE: i16 = 46;
|
||||||
const ICON_MARGIN: i16 = 4;
|
const ICON_MARGIN: i16 = 4;
|
||||||
const TEXT_MARGIN: i16 = 6;
|
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 }
|
Self { text, icon }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint(&self, area: Rect, style: &ButtonStyle, baseline_offset: i16) {
|
pub fn paint(&self, area: Rect, style: &ButtonStyle, baseline_offset: Offset) {
|
||||||
let width = style.font.text_width(self.text);
|
let width = style.font.text_width(self.text.as_ref());
|
||||||
let height = style.font.text_height();
|
|
||||||
|
|
||||||
let mut use_icon = false;
|
let mut use_icon = false;
|
||||||
let mut use_text = false;
|
let mut use_text = false;
|
||||||
@ -461,8 +563,7 @@ impl IconText {
|
|||||||
area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2),
|
area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2),
|
||||||
area.center().y,
|
area.center().y,
|
||||||
);
|
);
|
||||||
let mut text_pos =
|
let mut text_pos = area.left_center() + baseline_offset;
|
||||||
area.center() + Offset::new(-width / 2, height / 2) + Offset::y(baseline_offset);
|
|
||||||
|
|
||||||
if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) {
|
if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) {
|
||||||
//display both icon and text
|
//display both icon and text
|
||||||
@ -480,7 +581,7 @@ impl IconText {
|
|||||||
if use_text {
|
if use_text {
|
||||||
display::text_left(
|
display::text_left(
|
||||||
text_pos,
|
text_pos,
|
||||||
self.text,
|
self.text.as_ref(),
|
||||||
style.font,
|
style.font,
|
||||||
style.text_color,
|
style.text_color,
|
||||||
style.button_color,
|
style.button_color,
|
||||||
@ -496,16 +597,14 @@ impl IconText {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<'s>(
|
pub fn render<'s>(
|
||||||
& self,
|
& self,
|
||||||
target: &mut impl Renderer<'s>,
|
target: &mut impl Renderer<'s>,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
style: &ButtonStyle,
|
style: &ButtonStyle,
|
||||||
baseline_offset: i16,
|
baseline_offset: Offset,
|
||||||
) {
|
) {
|
||||||
let width = style.font.text_width(self.text);
|
let width = style.font.text_width(self.text.as_ref());
|
||||||
let height = style.font.text_height();
|
|
||||||
|
|
||||||
let mut use_icon = false;
|
let mut use_icon = false;
|
||||||
let mut use_text = false;
|
let mut use_text = false;
|
||||||
@ -514,8 +613,7 @@ impl IconText {
|
|||||||
area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2),
|
area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2),
|
||||||
area.center().y,
|
area.center().y,
|
||||||
);
|
);
|
||||||
let mut text_pos =
|
let mut text_pos = area.left_center() + baseline_offset;
|
||||||
area.center() + Offset::new(-width / 2, height / 2) + Offset::y(baseline_offset);
|
|
||||||
|
|
||||||
if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) {
|
if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) {
|
||||||
//display both icon and text
|
//display both icon and text
|
||||||
@ -531,12 +629,10 @@ impl IconText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if use_text {
|
if use_text {
|
||||||
shape::Text::new(text_pos, self.text)
|
shape::Text::new(text_pos, self.text.as_ref())
|
||||||
.with_font(style.font)
|
|
||||||
.with_fg(style.text_color)
|
.with_fg(style.text_color)
|
||||||
.render(target);
|
.render(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if use_icon {
|
if use_icon {
|
||||||
shape::ToifImage::new(icon_pos, self.icon.toif)
|
shape::ToifImage::new(icon_pos, self.icon.toif)
|
||||||
.with_align(Alignment2D::CENTER)
|
.with_align(Alignment2D::CENTER)
|
||||||
|
@ -62,8 +62,8 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_subtitle(mut self, subtitle: TString<'static>) -> Self {
|
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.title = Child::new(self.title.into_inner().top_aligned());
|
||||||
self.subtitle = Some(Child::new(Label::new(
|
self.subtitle = Some(Child::new(Label::new(
|
||||||
subtitle,
|
subtitle,
|
||||||
@ -127,7 +127,7 @@ where
|
|||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
let (mut header_area, content_area) = bounds.split_top(TITLE_HEIGHT);
|
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 {
|
if let Some(b) = &mut self.button {
|
||||||
let (rest, button_area) = header_area.split_right(TITLE_HEIGHT);
|
let (rest, button_area) = header_area.split_right(TITLE_HEIGHT);
|
||||||
|
@ -22,7 +22,7 @@ use crate::{
|
|||||||
ui::{
|
ui::{
|
||||||
constant::HEIGHT,
|
constant::HEIGHT,
|
||||||
display::{
|
display::{
|
||||||
tjpgd::{jpeg_test, BufferInput},
|
tjpgd::BufferInput,
|
||||||
toif::{Toif, ToifFormat},
|
toif::{Toif, ToifFormat},
|
||||||
},
|
},
|
||||||
model_mercury::component::homescreen::render::{
|
model_mercury::component::homescreen::render::{
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
pub mod bl_confirm;
|
pub mod bl_confirm;
|
||||||
mod button;
|
mod button;
|
||||||
|
#[cfg(feature = "translations")]
|
||||||
|
mod coinjoin_progress;
|
||||||
|
mod dialog;
|
||||||
|
mod fido;
|
||||||
|
mod vertical_menu;
|
||||||
|
#[rustfmt::skip]
|
||||||
|
mod fido_icons;
|
||||||
mod error;
|
mod error;
|
||||||
mod frame;
|
mod frame;
|
||||||
mod loader;
|
mod loader;
|
||||||
@ -13,6 +20,10 @@ pub use error::ErrorScreen;
|
|||||||
pub use frame::{Frame, FrameMsg};
|
pub use frame::{Frame, FrameMsg};
|
||||||
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
||||||
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
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;
|
pub use welcome_screen::WelcomeScreen;
|
||||||
|
|
||||||
use super::{constant, theme};
|
use super::{constant, theme};
|
||||||
|
@ -226,7 +226,7 @@ impl Component for NumberInput {
|
|||||||
let mut buf = [0u8; 10];
|
let mut buf = [0u8; 10];
|
||||||
if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) {
|
if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) {
|
||||||
let digit_font = Font::DEMIBOLD;
|
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::rect_fill(self.area, theme::BG);
|
||||||
display::text_center(
|
display::text_center(
|
||||||
self.area.center() + Offset::y(y_offset),
|
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) {
|
if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) {
|
||||||
let digit_font = Font::DEMIBOLD;
|
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::Bar::new(self.area).with_bg(theme::BG).render(target);
|
||||||
shape::Text::new(self.area.center() + Offset::y(y_offset), text)
|
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,
|
Border, Component, Empty, FormattedText, Label, Never, Qr, Timeout,
|
||||||
},
|
},
|
||||||
display::tjpgd::jpeg_info,
|
display::{tjpgd::jpeg_info, Icon},
|
||||||
geometry,
|
geometry,
|
||||||
layout::{
|
layout::{
|
||||||
obj::{ComponentMsgObj, LayoutObj},
|
obj::{ComponentMsgObj, LayoutObj},
|
||||||
@ -52,7 +52,8 @@ use super::{
|
|||||||
FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput,
|
FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput,
|
||||||
MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg,
|
MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg,
|
||||||
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
|
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
|
||||||
SelectWordCount, SelectWordCountMsg, SelectWordMsg, SimplePage, Slip39Input,
|
SelectWordCount, SelectWordCountMsg, SelectWordMsg, SimplePage, Slip39Input, VerticalMenu,
|
||||||
|
VerticalMenuChoiceMsg,
|
||||||
},
|
},
|
||||||
theme,
|
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>
|
impl<F, T, U> ComponentMsgObj for FidoConfirm<F, T, U>
|
||||||
where
|
where
|
||||||
F: Fn(usize) -> T,
|
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>
|
impl<T, U> ComponentMsgObj for ButtonPage<T, U>
|
||||||
where
|
where
|
||||||
T: Component + Paginate,
|
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_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
|
||||||
let words: [StrBuffer; 3] = util::iter_into_array(words_iterable)?;
|
let words: [StrBuffer; 3] = util::iter_into_array(words_iterable)?;
|
||||||
|
|
||||||
let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_DEMIBOLD, description)]);
|
let content = VerticalMenu::select_word(words);
|
||||||
let obj = LayoutObj::new(Frame::left_aligned(
|
let frame_with_menu = Frame::left_aligned(title, content).with_subtitle(description);
|
||||||
title,
|
let obj = LayoutObj::new(frame_with_menu)?;
|
||||||
Dialog::new(paragraphs, Button::select_word(words)),
|
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())
|
Ok(obj.into())
|
||||||
};
|
};
|
||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
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`."""
|
/// 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(),
|
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(
|
/// def show_share_words(
|
||||||
/// *,
|
/// *,
|
||||||
/// title: str,
|
/// 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);
|
pub const FATAL_ERROR_HIGHLIGHT_COLOR: Color = Color::rgb(0xFF, 0x41, 0x41);
|
||||||
|
|
||||||
// Commonly used corner radius (i.e. for buttons).
|
// Commonly used corner radius (i.e. for buttons).
|
||||||
pub const RADIUS: u8 = 2;
|
pub const RADIUS: u8 = 0;
|
||||||
|
|
||||||
// UI icons (greyscale).
|
// 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 {
|
pub const fn loader_default() -> LoaderStyleSheet {
|
||||||
LoaderStyleSheet {
|
LoaderStyleSheet {
|
||||||
normal: &LoaderStyle {
|
normal: &LoaderStyle {
|
||||||
|
@ -389,6 +389,12 @@ def select_word(
|
|||||||
iterable must be of exact size. Returns index in range `0..3`."""
|
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
|
# rust/src/ui/model_mercury/layout.rs
|
||||||
def show_share_words(
|
def show_share_words(
|
||||||
*,
|
*,
|
||||||
|
Loading…
Reference in New Issue
Block a user