mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-24 13:22:05 +00:00
feat(eckhart): implement hold to confirm anim
- HoldToConfirmAnim is driven by the ActionBar in case the right_button is configured with `long_press` - HoldToConfirmAnim optionally draws an Header overaly with custom text - disabling animations is respected - easing function is not yet finalized - a few minor fixes along the way
This commit is contained in:
parent
2e43b366f1
commit
b26484f5d1
@ -290,6 +290,7 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_instructions__hold_to_exit_tutorial;
|
MP_QSTR_instructions__hold_to_exit_tutorial;
|
||||||
MP_QSTR_instructions__hold_to_finish_tutorial;
|
MP_QSTR_instructions__hold_to_finish_tutorial;
|
||||||
MP_QSTR_instructions__hold_to_sign;
|
MP_QSTR_instructions__hold_to_sign;
|
||||||
|
MP_QSTR_instructions__keep_holding;
|
||||||
MP_QSTR_instructions__learn_more;
|
MP_QSTR_instructions__learn_more;
|
||||||
MP_QSTR_instructions__shares_continue_with_x_template;
|
MP_QSTR_instructions__shares_continue_with_x_template;
|
||||||
MP_QSTR_instructions__shares_start_with_1;
|
MP_QSTR_instructions__shares_start_with_1;
|
||||||
|
@ -158,7 +158,7 @@ pub enum Stopwatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Stopwatch {
|
impl Default for Stopwatch {
|
||||||
/// Returns a new sopteed stopwatch by default.
|
/// Returns a new stopped stopwatch by default.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new_stopped()
|
Self::new_stopped()
|
||||||
}
|
}
|
||||||
|
@ -1382,6 +1382,7 @@ pub enum TranslatedString {
|
|||||||
misc__enable_labeling = 973, // "Enable labeling?"
|
misc__enable_labeling = 973, // "Enable labeling?"
|
||||||
#[cfg(feature = "universal_fw")]
|
#[cfg(feature = "universal_fw")]
|
||||||
ethereum__unknown_contract_address_short = 974, // "Unknown contract address."
|
ethereum__unknown_contract_address_short = 974, // "Unknown contract address."
|
||||||
|
instructions__keep_holding = 975, // "Keep holding"
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TranslatedString {
|
impl TranslatedString {
|
||||||
@ -2760,6 +2761,7 @@ impl TranslatedString {
|
|||||||
Self::misc__enable_labeling => "Enable labeling?",
|
Self::misc__enable_labeling => "Enable labeling?",
|
||||||
#[cfg(feature = "universal_fw")]
|
#[cfg(feature = "universal_fw")]
|
||||||
Self::ethereum__unknown_contract_address_short => "Unknown contract address.",
|
Self::ethereum__unknown_contract_address_short => "Unknown contract address.",
|
||||||
|
Self::instructions__keep_holding => "Keep holding",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4137,6 +4139,7 @@ impl TranslatedString {
|
|||||||
Qstr::MP_QSTR_misc__enable_labeling => Some(Self::misc__enable_labeling),
|
Qstr::MP_QSTR_misc__enable_labeling => Some(Self::misc__enable_labeling),
|
||||||
#[cfg(feature = "universal_fw")]
|
#[cfg(feature = "universal_fw")]
|
||||||
Qstr::MP_QSTR_ethereum__unknown_contract_address_short => Some(Self::ethereum__unknown_contract_address_short),
|
Qstr::MP_QSTR_ethereum__unknown_contract_address_short => Some(Self::ethereum__unknown_contract_address_short),
|
||||||
|
Qstr::MP_QSTR_instructions__keep_holding => Some(Self::instructions__keep_holding),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
use crate::ui::{
|
use crate::{
|
||||||
component::{Component, Event, EventCtx},
|
translations::TR,
|
||||||
geometry::{Alignment2D, Offset, Rect},
|
ui::{
|
||||||
shape::{self, Renderer},
|
component::{Component, Event, EventCtx},
|
||||||
util::Pager,
|
geometry::{Alignment2D, Offset, Rect},
|
||||||
|
shape::{self, Renderer},
|
||||||
|
util::{animation_disabled, Pager},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
button::{Button, ButtonContent, ButtonMsg},
|
button::{Button, ButtonContent, ButtonMsg},
|
||||||
theme, ButtonStyleSheet,
|
theme, ButtonStyleSheet, HoldToConfirmAnim,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Component for control buttons in the bottom of the screen.
|
/// Component for control buttons in the bottom of the screen.
|
||||||
@ -25,7 +28,8 @@ pub struct ActionBar {
|
|||||||
// Storage of original button content for paginated component
|
// Storage of original button content for paginated component
|
||||||
left_original: Option<(ButtonContent, ButtonStyleSheet)>,
|
left_original: Option<(ButtonContent, ButtonStyleSheet)>,
|
||||||
right_original: Option<(ButtonContent, ButtonStyleSheet)>,
|
right_original: Option<(ButtonContent, ButtonStyleSheet)>,
|
||||||
// TODO: animation
|
/// Hold to confirm animation
|
||||||
|
htc_anim: Option<HoldToConfirmAnim>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ActionBarMsg {
|
pub enum ActionBarMsg {
|
||||||
@ -129,6 +133,15 @@ impl ActionBar {
|
|||||||
_ => (None, None),
|
_ => (None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let htc_anim = right_button
|
||||||
|
.long_press()
|
||||||
|
.filter(|_| !animation_disabled())
|
||||||
|
.map(|dur| {
|
||||||
|
HoldToConfirmAnim::new()
|
||||||
|
.with_duration(dur)
|
||||||
|
.with_header_overlay(TR::instructions__keep_holding.into())
|
||||||
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
mode,
|
mode,
|
||||||
right_button,
|
right_button,
|
||||||
@ -137,8 +150,57 @@ impl ActionBar {
|
|||||||
left_short: false,
|
left_short: false,
|
||||||
left_original,
|
left_original,
|
||||||
right_original,
|
right_original,
|
||||||
|
htc_anim,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle right button at the last page, this includes:
|
||||||
|
/// - Single button mode
|
||||||
|
/// - Double button mode at single page component
|
||||||
|
/// - Double button mode at last page of paginated component
|
||||||
|
/// The function takes care about triggering the correct action to
|
||||||
|
/// HoldToConfirm or returning the correct message out of the ActionBar.
|
||||||
|
fn right_button_at_last_page(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut EventCtx,
|
||||||
|
msg: ButtonMsg,
|
||||||
|
) -> Option<ActionBarMsg> {
|
||||||
|
let is_hold = self.right_button.long_press().is_some();
|
||||||
|
match (msg, is_hold) {
|
||||||
|
(ButtonMsg::Pressed, true) => {
|
||||||
|
if let Some(htc_anim) = &mut self.htc_anim {
|
||||||
|
htc_anim.start();
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
ctx.request_paint();
|
||||||
|
ctx.disable_swipe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(ButtonMsg::Clicked, true) => {
|
||||||
|
if let Some(htc_anim) = &mut self.htc_anim {
|
||||||
|
htc_anim.stop();
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
ctx.request_paint();
|
||||||
|
ctx.enable_swipe();
|
||||||
|
} else {
|
||||||
|
// Animations disabled, return confirmed
|
||||||
|
return Some(ActionBarMsg::Confirmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(ButtonMsg::Released, true) => {
|
||||||
|
if let Some(htc_anim) = &mut self.htc_anim {
|
||||||
|
htc_anim.stop();
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
ctx.request_paint();
|
||||||
|
ctx.enable_swipe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(ButtonMsg::Clicked, false) | (ButtonMsg::LongPressed, true) => {
|
||||||
|
return Some(ActionBarMsg::Confirmed);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for ActionBar {
|
impl Component for ActionBar {
|
||||||
@ -172,60 +234,43 @@ impl Component for ActionBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
self.htc_anim.event(ctx, event);
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Single => {
|
Mode::Single => {
|
||||||
// Only handle confirm button
|
// Only handle confirm button
|
||||||
if let Some(ButtonMsg::Clicked) = self.right_button.event(ctx, event) {
|
if let Some(msg) = self.right_button.event(ctx, event) {
|
||||||
return Some(ActionBarMsg::Confirmed);
|
return self.right_button_at_last_page(ctx, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::Double { pager } => {
|
Mode::Double { pager } => {
|
||||||
if pager.is_single() {
|
if pager.is_single() {
|
||||||
// Single page - show back and confirm
|
// Single page - show back and confirm
|
||||||
if let Some(btn) = &mut self.left_button {
|
if let Some(ButtonMsg::Clicked) = self.left_button.event(ctx, event) {
|
||||||
if let Some(ButtonMsg::Clicked) = btn.event(ctx, event) {
|
return Some(ActionBarMsg::Cancelled);
|
||||||
return Some(ActionBarMsg::Cancelled);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(msg) = self.right_button.event(ctx, event) {
|
if let Some(msg) = self.right_button.event(ctx, event) {
|
||||||
match (&self.right_button.is_long_press(), msg) {
|
return self.right_button_at_last_page(ctx, msg);
|
||||||
(true, ButtonMsg::LongPressed) | (false, ButtonMsg::Clicked) => {
|
|
||||||
return Some(ActionBarMsg::Confirmed);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if pager.is_first() && !pager.is_single() {
|
} else if pager.is_first() && !pager.is_single() {
|
||||||
// First page of multiple - go back and next page
|
// First page of multiple - go back and next page
|
||||||
if let Some(btn) = &mut self.left_button {
|
if let Some(ButtonMsg::Clicked) = self.left_button.event(ctx, event) {
|
||||||
if let Some(ButtonMsg::Clicked) = btn.event(ctx, event) {
|
return Some(ActionBarMsg::Cancelled);
|
||||||
return Some(ActionBarMsg::Cancelled);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(ButtonMsg::Clicked) = self.right_button.event(ctx, event) {
|
if let Some(ButtonMsg::Clicked) = self.right_button.event(ctx, event) {
|
||||||
return Some(ActionBarMsg::Next);
|
return Some(ActionBarMsg::Next);
|
||||||
}
|
}
|
||||||
} else if pager.is_last() && !pager.is_single() {
|
} else if pager.is_last() && !pager.is_single() {
|
||||||
// Last page - enable up button, show confirm
|
// Last page - enable up button, show confirm
|
||||||
if let Some(btn) = &mut self.left_button {
|
if let Some(ButtonMsg::Clicked) = self.left_button.event(ctx, event) {
|
||||||
if let Some(ButtonMsg::Clicked) = btn.event(ctx, event) {
|
return Some(ActionBarMsg::Prev);
|
||||||
return Some(ActionBarMsg::Prev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(msg) = self.right_button.event(ctx, event) {
|
if let Some(msg) = self.right_button.event(ctx, event) {
|
||||||
match (&self.right_button.is_long_press(), msg) {
|
return self.right_button_at_last_page(ctx, msg);
|
||||||
(true, ButtonMsg::LongPressed) | (false, ButtonMsg::Clicked) => {
|
|
||||||
return Some(ActionBarMsg::Confirmed);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Middle pages - navigations up/down
|
// Middle pages - navigations up/down
|
||||||
if let Some(btn) = &mut self.left_button {
|
if let Some(ButtonMsg::Clicked) = self.left_button.event(ctx, event) {
|
||||||
if let Some(ButtonMsg::Clicked) = btn.event(ctx, event) {
|
return Some(ActionBarMsg::Prev);
|
||||||
return Some(ActionBarMsg::Prev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(ButtonMsg::Clicked) = self.right_button.event(ctx, event) {
|
if let Some(ButtonMsg::Clicked) = self.right_button.event(ctx, event) {
|
||||||
return Some(ActionBarMsg::Next);
|
return Some(ActionBarMsg::Next);
|
||||||
@ -246,8 +291,12 @@ impl Component for ActionBar {
|
|||||||
btn.render(target);
|
btn.render(target);
|
||||||
}
|
}
|
||||||
self.right_button.render(target);
|
self.right_button.render(target);
|
||||||
|
if let Some(htc_anim) = &self.htc_anim {
|
||||||
|
htc_anim.render(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl crate::trace::Trace for ActionBar {
|
impl crate::trace::Trace for ActionBar {
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
@ -135,8 +135,8 @@ impl Button {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_long_press(&self) -> bool {
|
pub fn long_press(&self) -> Option<Duration> {
|
||||||
self.long_press.is_some()
|
self.long_press
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_disabled(&self) -> bool {
|
pub fn is_disabled(&self) -> bool {
|
||||||
|
@ -171,7 +171,8 @@ impl Header {
|
|||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the width needed for the left icon, be it a button with icon or just icon
|
/// Calculates the width needed for the left icon, be it a button with icon
|
||||||
|
/// or just icon
|
||||||
fn left_icon_width(&self) -> i16 {
|
fn left_icon_width(&self) -> i16 {
|
||||||
let margin_right: i16 = 16; // [px]
|
let margin_right: i16 = 16; // [px]
|
||||||
if let Some(b) = &self.left_button {
|
if let Some(b) = &self.left_button {
|
||||||
|
@ -245,7 +245,6 @@ impl<'a> Instruction<'a> {
|
|||||||
fn height(&self) -> i16 {
|
fn height(&self) -> i16 {
|
||||||
let text_area_width = screen().inset(Hint::HINT_INSETS).width() - self.icon_width();
|
let text_area_width = screen().inset(Hint::HINT_INSETS).width() - self.icon_width();
|
||||||
let calculated_height = self.label.text_height(text_area_width);
|
let calculated_height = self.label.text_height(text_area_width);
|
||||||
dbg_print!("Instruction height: {}\n", calculated_height as i16);
|
|
||||||
debug_assert!(calculated_height <= Hint::HEIGHT_MAXIMAL);
|
debug_assert!(calculated_height <= Hint::HEIGHT_MAXIMAL);
|
||||||
calculated_height
|
calculated_height
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
use crate::{
|
||||||
|
strutil::TString,
|
||||||
|
time::{Duration, Stopwatch},
|
||||||
|
ui::{
|
||||||
|
component::{Component, Event, EventCtx, Never},
|
||||||
|
display::Color,
|
||||||
|
geometry::{Offset, Rect},
|
||||||
|
layout_eckhart::{cshape::ScreenBorder, fonts},
|
||||||
|
shape::{self, Renderer},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{constant, theme, Header};
|
||||||
|
|
||||||
|
/// A component that displays a border that grows from the bottom of the screen
|
||||||
|
/// to the top. The animation is parametrizable by color and duration.
|
||||||
|
pub struct HoldToConfirmAnim {
|
||||||
|
/// Duration of the animation
|
||||||
|
duration: Duration,
|
||||||
|
/// Screen border and header overlay color
|
||||||
|
color: Color,
|
||||||
|
/// Screen border shape
|
||||||
|
border: ScreenBorder,
|
||||||
|
/// Timer for the animation
|
||||||
|
timer: Stopwatch,
|
||||||
|
/// Header overlay text shown during the animation
|
||||||
|
header_overlay: Option<TString<'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HoldToConfirmAnim {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let default_color = theme::GREEN_LIME;
|
||||||
|
Self {
|
||||||
|
duration: theme::CONFIRM_HOLD_DURATION,
|
||||||
|
color: default_color,
|
||||||
|
border: ScreenBorder::new(default_color),
|
||||||
|
timer: Stopwatch::default(),
|
||||||
|
header_overlay: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_color(mut self, color: Color) -> Self {
|
||||||
|
self.color = color;
|
||||||
|
self.border = ScreenBorder::new(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_duration(mut self, duration: Duration) -> Self {
|
||||||
|
self.duration = duration;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_header_overlay(mut self, header_text: TString<'static>) -> Self {
|
||||||
|
self.header_overlay = Some(header_text);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
self.timer = Stopwatch::new_started();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.timer = Stopwatch::new_stopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_active(&self) -> bool {
|
||||||
|
self.timer.is_running_within(self.duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_clip(&self) -> Rect {
|
||||||
|
// TODO:
|
||||||
|
// 1) there will be some easer function
|
||||||
|
// 2) the growth of the top bar cannot be done with just one clip
|
||||||
|
let screen = constant::screen();
|
||||||
|
let ratio = self.timer.elapsed() / self.duration;
|
||||||
|
let clip_height = ((ratio * screen.height() as f32) as i16).clamp(0, screen.height());
|
||||||
|
Rect::from_bottom_left_and_size(
|
||||||
|
screen.bottom_left(),
|
||||||
|
Offset::new(screen.width(), clip_height),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for HoldToConfirmAnim {
|
||||||
|
type Msg = Never;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||||
|
if self.is_active() {
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
ctx.request_paint();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||||
|
if self.is_active() {
|
||||||
|
// override header with custom text
|
||||||
|
if let Some(text) = self.header_overlay {
|
||||||
|
let font = fonts::FONT_SATOSHI_REGULAR_22;
|
||||||
|
let header_pad = Rect::from_top_left_and_size(
|
||||||
|
constant::screen().top_left(),
|
||||||
|
Offset::new(constant::screen().width(), Header::HEADER_HEIGHT),
|
||||||
|
);
|
||||||
|
shape::Bar::new(header_pad)
|
||||||
|
.with_bg(theme::BG)
|
||||||
|
.render(target);
|
||||||
|
text.map(|text| {
|
||||||
|
let text_pos = header_pad.top_left()
|
||||||
|
+ Offset::new(24, font.vert_center(0, Header::HEADER_HEIGHT, text));
|
||||||
|
shape::Text::new(text_pos, text, font)
|
||||||
|
.with_fg(self.color)
|
||||||
|
.render(target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// growing border
|
||||||
|
let clip = self.get_clip();
|
||||||
|
target.in_clip(clip, &|target| {
|
||||||
|
self.border.render(target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ mod button;
|
|||||||
mod error;
|
mod error;
|
||||||
mod header;
|
mod header;
|
||||||
mod hint;
|
mod hint;
|
||||||
|
mod hold_to_confirm;
|
||||||
mod result;
|
mod result;
|
||||||
mod text_screen;
|
mod text_screen;
|
||||||
mod vertical_menu;
|
mod vertical_menu;
|
||||||
@ -15,6 +16,7 @@ pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet
|
|||||||
pub use error::ErrorScreen;
|
pub use error::ErrorScreen;
|
||||||
pub use header::{Header, HeaderMsg};
|
pub use header::{Header, HeaderMsg};
|
||||||
pub use hint::Hint;
|
pub use hint::Hint;
|
||||||
|
pub use hold_to_confirm::HoldToConfirmAnim;
|
||||||
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
||||||
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
|
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
|
||||||
pub use vertical_menu::{VerticalMenu, VerticalMenuMsg, MENU_MAX_ITEMS};
|
pub use vertical_menu::{VerticalMenu, VerticalMenuMsg, MENU_MAX_ITEMS};
|
||||||
|
@ -19,7 +19,8 @@ impl ScreenBorder {
|
|||||||
pub fn new(color: Color) -> Self {
|
pub fn new(color: Color) -> Self {
|
||||||
let screen = constant::screen();
|
let screen = constant::screen();
|
||||||
|
|
||||||
// Top bar: from the right edge of top-left icon to the left edge of top-right icon.
|
// Top bar: from the right edge of top-left icon to the left edge of top-right
|
||||||
|
// icon.
|
||||||
let top_bar_rect = Rect {
|
let top_bar_rect = Rect {
|
||||||
x0: screen.x0 + ICON_BORDER_TL.toif.width(),
|
x0: screen.x0 + ICON_BORDER_TL.toif.width(),
|
||||||
y0: screen.y0,
|
y0: screen.y0,
|
||||||
@ -27,7 +28,8 @@ impl ScreenBorder {
|
|||||||
y1: screen.y0 + 2,
|
y1: screen.y0 + 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bottom bar: from the right edge of bottom-left icon to the left edge of bottom-right icon.
|
// Bottom bar: from the right edge of bottom-left icon to the left edge of
|
||||||
|
// bottom-right icon.
|
||||||
let bottom_bar_rect = Rect {
|
let bottom_bar_rect = Rect {
|
||||||
x0: screen.x0 + ICON_BORDER_BL.toif.width(),
|
x0: screen.x0 + ICON_BORDER_BL.toif.width(),
|
||||||
y0: screen.y1 - 2,
|
y0: screen.y1 - 2,
|
||||||
@ -35,14 +37,16 @@ impl ScreenBorder {
|
|||||||
y1: screen.y1,
|
y1: screen.y1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Left bar: from the bottom edge of top-left icon to the top edge of bottom-left icon.
|
// Left bar: from the bottom edge of top-left icon to the top edge of
|
||||||
|
// bottom-left icon.
|
||||||
let left_bar_rect = Rect {
|
let left_bar_rect = Rect {
|
||||||
x0: screen.x0,
|
x0: screen.x0,
|
||||||
y0: screen.y0 + ICON_BORDER_TL.toif.height() - 1,
|
y0: screen.y0 + ICON_BORDER_TL.toif.height() - 1,
|
||||||
x1: screen.x0 + 2,
|
x1: screen.x0 + 2,
|
||||||
y1: screen.y1 - ICON_BORDER_BL.toif.height(),
|
y1: screen.y1 - ICON_BORDER_BL.toif.height(),
|
||||||
};
|
};
|
||||||
// Right bar: from the bottom edge of top-right icon to the top edge of bottom-right icon.
|
// Right bar: from the bottom edge of top-right icon to the top edge of
|
||||||
|
// bottom-right icon.
|
||||||
let right_bar_rect = Rect {
|
let right_bar_rect = Rect {
|
||||||
x0: screen.x1 - 2,
|
x0: screen.x1 - 2,
|
||||||
y0: screen.y0 + ICON_BORDER_TR.toif.height() - 1,
|
y0: screen.y0 + ICON_BORDER_TR.toif.height() - 1,
|
||||||
|
@ -631,7 +631,7 @@ def show_success(
|
|||||||
title: str,
|
title: str,
|
||||||
button: str,
|
button: str,
|
||||||
description: str = "",
|
description: str = "",
|
||||||
allow_cancel: bool = True,
|
allow_cancel: bool = False,
|
||||||
time_ms: int = 0,
|
time_ms: int = 0,
|
||||||
) -> LayoutObj[UiResult]:
|
) -> LayoutObj[UiResult]:
|
||||||
"""Success modal. No buttons shown when `button` is empty string."""
|
"""Success modal. No buttons shown when `button` is empty string."""
|
||||||
|
@ -393,6 +393,7 @@ class TR:
|
|||||||
instructions__hold_to_continue: str = "Hold to continue"
|
instructions__hold_to_continue: str = "Hold to continue"
|
||||||
instructions__hold_to_exit_tutorial: str = "Hold to exit tutorial"
|
instructions__hold_to_exit_tutorial: str = "Hold to exit tutorial"
|
||||||
instructions__hold_to_sign: str = "Hold to sign"
|
instructions__hold_to_sign: str = "Hold to sign"
|
||||||
|
instructions__keep_holding: str = "Keep holding"
|
||||||
instructions__learn_more: str = "Learn more"
|
instructions__learn_more: str = "Learn more"
|
||||||
instructions__shares_continue_with_x_template: str = "Continue with Share #{0}"
|
instructions__shares_continue_with_x_template: str = "Continue with Share #{0}"
|
||||||
instructions__shares_start_with_1: str = "Start with share #1"
|
instructions__shares_start_with_1: str = "Start with share #1"
|
||||||
|
@ -395,6 +395,7 @@
|
|||||||
"instructions__hold_to_continue": "Hold to continue",
|
"instructions__hold_to_continue": "Hold to continue",
|
||||||
"instructions__hold_to_exit_tutorial": "Hold to exit tutorial",
|
"instructions__hold_to_exit_tutorial": "Hold to exit tutorial",
|
||||||
"instructions__hold_to_sign": "Hold to sign",
|
"instructions__hold_to_sign": "Hold to sign",
|
||||||
|
"instructions__keep_holding": "Keep holding",
|
||||||
"instructions__learn_more": "Learn more",
|
"instructions__learn_more": "Learn more",
|
||||||
"instructions__shares_continue_with_x_template": "Continue with Share #{0}",
|
"instructions__shares_continue_with_x_template": "Continue with Share #{0}",
|
||||||
"instructions__shares_start_with_1": "Start with share #1",
|
"instructions__shares_start_with_1": "Start with share #1",
|
||||||
|
@ -973,5 +973,6 @@
|
|||||||
"971": "instructions__view_all_data",
|
"971": "instructions__view_all_data",
|
||||||
"972": "ethereum__interaction_contract",
|
"972": "ethereum__interaction_contract",
|
||||||
"973": "misc__enable_labeling",
|
"973": "misc__enable_labeling",
|
||||||
"974": "ethereum__unknown_contract_address_short"
|
"974": "ethereum__unknown_contract_address_short",
|
||||||
|
"975": "instructions__keep_holding"
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"current": {
|
"current": {
|
||||||
"merkle_root": "6b7c949ee3a2332eca6a3224cab4f161b7b0ae8e4b8c54591ec50731091b99c9",
|
"merkle_root": "43c08e81d71c1c28d77b1650fc96b0dfcd473fde0c922717e5588baeeb581bd3",
|
||||||
"datetime": "2025-02-07T14:30:18.145419",
|
"datetime": "2025-02-14T16:12:57.065880",
|
||||||
"commit": "061e71213ea8340874e47eab7d0aec07ec444c1e"
|
"commit": "3dabb94653e04856efc89d07c67b7e6f0c587f8c"
|
||||||
},
|
},
|
||||||
"history": [
|
"history": [
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user