mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-30 01:01:00 +00:00
feat(core): add loader to homescreen when locking the device for TS3
This commit is contained in:
parent
5d8e56ac2a
commit
76c547bb91
1
core/.changelog.d/3440.added
Normal file
1
core/.changelog.d/3440.added
Normal file
@ -0,0 +1 @@
|
||||
[T2B1] Add loader to homescreen when locking the device
|
BIN
core/assets/model_r/lock_small.png
Normal file
BIN
core/assets/model_r/lock_small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 165 B |
@ -47,6 +47,10 @@ pub trait Component {
|
||||
/// Component can also optionally return a message as a result of the
|
||||
/// interaction.
|
||||
///
|
||||
/// For all components to work properly (e.g. react to `ctx.request_paint`),
|
||||
/// it is required to call `event` function to them, even if they never
|
||||
/// return a message.
|
||||
///
|
||||
/// No painting should be done in this phase.
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg>;
|
||||
|
||||
|
@ -5,6 +5,7 @@ use crate::{
|
||||
component::{Component, Event, EventCtx, Never},
|
||||
constant,
|
||||
display::{self, Color, Font, Icon},
|
||||
event::PhysicalButton,
|
||||
geometry::{Alignment2D, Offset, Point, Rect},
|
||||
},
|
||||
};
|
||||
@ -20,6 +21,15 @@ pub enum ButtonPos {
|
||||
Right,
|
||||
}
|
||||
|
||||
impl From<PhysicalButton> for ButtonPos {
|
||||
fn from(btn: PhysicalButton) -> Self {
|
||||
match btn {
|
||||
PhysicalButton::Left => ButtonPos::Left,
|
||||
PhysicalButton::Right => ButtonPos::Right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Button<T>
|
||||
where
|
||||
T: StringType,
|
||||
|
@ -46,6 +46,9 @@ pub enum ButtonControllerMsg {
|
||||
Triggered(ButtonPos, bool),
|
||||
/// Button was pressed and held for longer time (not released yet).
|
||||
LongPressed(ButtonPos),
|
||||
/// Hold-to-confirm button was released prematurely - without triggering
|
||||
/// LongPressed.
|
||||
ReleasedWithoutLongPress(ButtonPos),
|
||||
}
|
||||
|
||||
/// Defines what kind of button should be currently used.
|
||||
@ -186,10 +189,11 @@ where
|
||||
self.long_pressed_timer = None;
|
||||
Some(ButtonControllerMsg::Triggered(self.pos, long_press))
|
||||
}
|
||||
_ => {
|
||||
ButtonType::HoldToConfirm(_) => {
|
||||
self.hold_ended(ctx);
|
||||
None
|
||||
Some(ButtonControllerMsg::ReleasedWithoutLongPress(self.pos))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,6 +273,8 @@ where
|
||||
button_area: Rect,
|
||||
/// Handling optional ignoring of buttons after pressing the other button.
|
||||
ignore_btn_delay: Option<IgnoreButtonDelay>,
|
||||
/// Whether to count with middle button
|
||||
handle_middle_button: bool,
|
||||
}
|
||||
|
||||
impl<T> ButtonController<T>
|
||||
@ -276,6 +282,7 @@ where
|
||||
T: StringType,
|
||||
{
|
||||
pub fn new(btn_layout: ButtonLayout<T>) -> Self {
|
||||
let handle_middle_button = btn_layout.btn_middle.is_some();
|
||||
Self {
|
||||
pad: Pad::with_background(theme::BG).with_clear(),
|
||||
left_btn: ButtonContainer::new(ButtonPos::Left, btn_layout.btn_left),
|
||||
@ -284,6 +291,7 @@ where
|
||||
state: ButtonState::Nothing,
|
||||
button_area: Rect::zero(),
|
||||
ignore_btn_delay: None,
|
||||
handle_middle_button,
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,6 +304,7 @@ where
|
||||
|
||||
/// Updating all the three buttons to the wanted states.
|
||||
pub fn set(&mut self, btn_layout: ButtonLayout<T>) {
|
||||
self.handle_middle_button = btn_layout.btn_middle.is_some();
|
||||
self.pad.clear();
|
||||
self.left_btn.set(btn_layout.btn_left, self.button_area);
|
||||
self.middle_btn.set(btn_layout.btn_middle, self.button_area);
|
||||
@ -471,13 +480,21 @@ where
|
||||
return None;
|
||||
}
|
||||
}
|
||||
self.got_pressed(ctx, ButtonPos::Middle);
|
||||
self.middle_hold_started(ctx);
|
||||
(
|
||||
// ↓ ↓
|
||||
ButtonState::BothDown,
|
||||
Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)),
|
||||
)
|
||||
// ↓ ↓
|
||||
if self.handle_middle_button {
|
||||
self.got_pressed(ctx, ButtonPos::Middle);
|
||||
self.middle_hold_started(ctx);
|
||||
(
|
||||
ButtonState::BothDown,
|
||||
Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)),
|
||||
)
|
||||
} else {
|
||||
self.got_pressed(ctx, b.into());
|
||||
(
|
||||
ButtonState::BothDown,
|
||||
Some(ButtonControllerMsg::Pressed(b.into())),
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => (self.state, None),
|
||||
},
|
||||
@ -487,7 +504,14 @@ where
|
||||
ButtonEvent::ButtonReleased(b) => {
|
||||
self.middle_btn.hold_ended(ctx);
|
||||
// _ ↓ | ↓ _
|
||||
(ButtonState::OneReleased(b), None)
|
||||
if self.handle_middle_button {
|
||||
(ButtonState::OneReleased(b), None)
|
||||
} else {
|
||||
(
|
||||
ButtonState::OneReleased(b),
|
||||
Some(ButtonControllerMsg::Triggered(b.into(), false)),
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => (self.state, None),
|
||||
},
|
||||
@ -507,7 +531,14 @@ where
|
||||
ignore_btn_delay.make_button_clickable(ButtonPos::Left);
|
||||
ignore_btn_delay.make_button_clickable(ButtonPos::Right);
|
||||
}
|
||||
(ButtonState::Nothing, self.middle_btn.maybe_trigger(ctx))
|
||||
if self.handle_middle_button {
|
||||
(ButtonState::Nothing, self.middle_btn.maybe_trigger(ctx))
|
||||
} else {
|
||||
(
|
||||
ButtonState::Nothing,
|
||||
Some(ButtonControllerMsg::Triggered(b.into(), false)),
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => (self.state, None),
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
component::{Child, Component, Event, EventCtx, Label},
|
||||
constant::{HEIGHT, WIDTH},
|
||||
display::{
|
||||
rect_fill,
|
||||
self, rect_fill,
|
||||
toif::{Toif, ToifFormat},
|
||||
Font, Icon,
|
||||
},
|
||||
@ -17,7 +17,7 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
super::constant, common::display_center, theme, ButtonController, ButtonControllerMsg,
|
||||
ButtonLayout, ButtonPos, CancelConfirmMsg,
|
||||
ButtonLayout, ButtonPos, CancelConfirmMsg, LoaderMsg, ProgressLoader,
|
||||
};
|
||||
|
||||
const AREA: Rect = constant::screen();
|
||||
@ -34,6 +34,8 @@ const NOTIFICATION_FONT: Font = Font::NORMAL;
|
||||
const NOTIFICATION_ICON: Icon = theme::ICON_WARNING;
|
||||
const COINJOIN_CORNER: Point = AREA.top_right().ofs(Offset::new(-2, 2));
|
||||
|
||||
const HOLD_TO_LOCK_MS: u32 = 1000;
|
||||
|
||||
fn paint_default_image() {
|
||||
theme::ICON_LOGO.draw(
|
||||
TOP_CENTER + Offset::y(LOGO_ICON_TOP_MARGIN),
|
||||
@ -43,6 +45,12 @@ fn paint_default_image() {
|
||||
);
|
||||
}
|
||||
|
||||
enum CurrentScreen {
|
||||
EmptyAtStart,
|
||||
Homescreen,
|
||||
Loader,
|
||||
}
|
||||
|
||||
pub struct Homescreen<T>
|
||||
where
|
||||
T: StringType,
|
||||
@ -53,18 +61,31 @@ where
|
||||
notification: Option<(T, u8)>,
|
||||
/// Used for HTC functionality to lock device from homescreen
|
||||
invisible_buttons: Child<ButtonController<T>>,
|
||||
/// Holds the loader component
|
||||
loader: Option<Child<ProgressLoader<T>>>,
|
||||
/// Whether to show the loader or not
|
||||
show_loader: bool,
|
||||
/// Which screen is currently shown
|
||||
current_screen: CurrentScreen,
|
||||
}
|
||||
|
||||
impl<T> Homescreen<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
pub fn new(label: T, notification: Option<(T, u8)>) -> Self {
|
||||
let invisible_btn_layout = ButtonLayout::htc_none_htc("".into(), "".into());
|
||||
pub fn new(label: T, notification: Option<(T, u8)>, loader_description: Option<T>) -> Self {
|
||||
// Buttons will not be visible, we only need both left and right to be existing
|
||||
// so we can get the events from them.
|
||||
let invisible_btn_layout = ButtonLayout::text_none_text("".into(), "".into());
|
||||
let loader =
|
||||
loader_description.map(|desc| Child::new(ProgressLoader::new(desc, HOLD_TO_LOCK_MS)));
|
||||
Self {
|
||||
label: Label::centered(label, theme::TEXT_BIG),
|
||||
notification,
|
||||
invisible_buttons: Child::new(ButtonController::new(invisible_btn_layout)),
|
||||
loader,
|
||||
show_loader: false,
|
||||
current_screen: CurrentScreen::EmptyAtStart,
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,24 +173,64 @@ where
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.label.place(LABEL_AREA);
|
||||
self.loader.place(AREA);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
Self::event_usb(self, ctx, event);
|
||||
// HTC press of any button will lock the device
|
||||
if let Some(ButtonControllerMsg::Triggered(..)) = self.invisible_buttons.event(ctx, event) {
|
||||
return Some(());
|
||||
|
||||
// Only care about button and loader events when there is a possibility of
|
||||
// locking the device
|
||||
if let Some(self_loader) = &mut self.loader {
|
||||
// When loader has completely grown, we can lock the device
|
||||
if let Some(LoaderMsg::GrownCompletely) = self_loader.event(ctx, event) {
|
||||
return Some(());
|
||||
}
|
||||
|
||||
// Longer hold of any button will lock the device.
|
||||
// Normal/quick presses and releases will show/hide the loader.
|
||||
let button_event = self.invisible_buttons.event(ctx, event);
|
||||
if let Some(ButtonControllerMsg::Pressed(..)) = button_event {
|
||||
if !self.show_loader {
|
||||
self.show_loader = true;
|
||||
self_loader.mutate(ctx, |ctx, loader| {
|
||||
loader.start(ctx);
|
||||
ctx.request_paint();
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(ButtonControllerMsg::Triggered(..)) = button_event {
|
||||
self.show_loader = false;
|
||||
self_loader.mutate(ctx, |ctx, loader| {
|
||||
loader.stop(ctx);
|
||||
ctx.request_paint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
// Painting the homescreen image first, as the notification and label
|
||||
// should be "on top of it"
|
||||
self.paint_homescreen_image();
|
||||
self.paint_notification();
|
||||
self.paint_label();
|
||||
// Redraw the whole screen when the screen changes (loader vs homescreen)
|
||||
if self.show_loader {
|
||||
if !matches!(self.current_screen, CurrentScreen::Loader) {
|
||||
display::clear();
|
||||
self.current_screen = CurrentScreen::Loader;
|
||||
}
|
||||
self.loader.paint();
|
||||
} else {
|
||||
if !matches!(self.current_screen, CurrentScreen::Homescreen) {
|
||||
display::clear();
|
||||
self.current_screen = CurrentScreen::Homescreen;
|
||||
}
|
||||
// Painting the homescreen image first, as the notification and label
|
||||
// should be "on top of it"
|
||||
self.paint_homescreen_image();
|
||||
self.paint_notification();
|
||||
self.paint_label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,15 @@ use crate::{
|
||||
time::{Duration, Instant},
|
||||
ui::{
|
||||
animation::Animation,
|
||||
component::{Component, Event, EventCtx},
|
||||
display::{self, Color, Font},
|
||||
component::{Child, Component, Event, EventCtx},
|
||||
constant,
|
||||
display::{self, Color, Font, LOADER_MAX},
|
||||
geometry::{Offset, Rect},
|
||||
util::animation_disabled,
|
||||
},
|
||||
};
|
||||
|
||||
use super::theme;
|
||||
use super::{theme, Progress};
|
||||
|
||||
pub const DEFAULT_DURATION_MS: u32 = 1000;
|
||||
pub const SHRINKING_DURATION_MS: u32 = 500;
|
||||
@ -186,10 +187,9 @@ where
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let now = Instant::now();
|
||||
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
if self.is_animating() {
|
||||
let now = Instant::now();
|
||||
if self.is_completely_grown(now) {
|
||||
self.state = State::Grown;
|
||||
ctx.request_paint();
|
||||
@ -256,6 +256,93 @@ impl LoaderStyleSheet {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProgressLoader<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
loader: Child<Progress<T>>,
|
||||
duration_ms: u32,
|
||||
start_time: Option<Instant>,
|
||||
}
|
||||
|
||||
impl<T> ProgressLoader<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
const LOADER_FRAMES_DEFAULT: u32 = 20;
|
||||
|
||||
pub fn new(loader_description: T, duration_ms: u32) -> Self {
|
||||
Self {
|
||||
loader: Child::new(
|
||||
Progress::new(false, loader_description).with_icon(theme::ICON_LOCK_SMALL),
|
||||
),
|
||||
duration_ms,
|
||||
start_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, ctx: &mut EventCtx) {
|
||||
self.start_time = Some(Instant::now());
|
||||
self.loader.event(ctx, Event::Progress(0, ""));
|
||||
self.loader.mutate(ctx, |ctx, loader| {
|
||||
loader.request_paint(ctx);
|
||||
});
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, _ctx: &mut EventCtx) {
|
||||
self.start_time = None;
|
||||
}
|
||||
|
||||
fn is_animating(&self) -> bool {
|
||||
self.start_time.is_some()
|
||||
}
|
||||
|
||||
fn percentage(&self, now: Instant) -> u32 {
|
||||
if let Some(start_time) = self.start_time {
|
||||
let elapsed = now.saturating_duration_since(start_time);
|
||||
let elapsed_ms = elapsed.to_millis();
|
||||
(elapsed_ms * 100) / self.duration_ms
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for ProgressLoader<T>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
type Msg = LoaderMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.loader.place(constant::screen());
|
||||
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_animating() {
|
||||
let now = Instant::now();
|
||||
let percentage = self.percentage(now);
|
||||
let new_loader_value = (percentage * LOADER_MAX as u32) / 100;
|
||||
self.loader
|
||||
.event(ctx, Event::Progress(new_loader_value as u16, ""));
|
||||
// Returning only after the loader was fully painted
|
||||
if percentage >= 100 {
|
||||
return Some(LoaderMsg::GrownCompletely);
|
||||
}
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
self.loader.paint();
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
|
@ -22,7 +22,7 @@ pub use input_methods::{
|
||||
choice::{Choice, ChoiceFactory, ChoicePage},
|
||||
choice_item::ChoiceItem,
|
||||
};
|
||||
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
||||
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet, ProgressLoader};
|
||||
pub use result::ResultScreen;
|
||||
pub use welcome_screen::WelcomeScreen;
|
||||
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||
Child, Component, Event, EventCtx, Label, Never, Pad,
|
||||
},
|
||||
constant,
|
||||
display::{self, Font},
|
||||
display::{self, Font, Icon, LOADER_MAX},
|
||||
geometry::Rect,
|
||||
util::animation_disabled,
|
||||
},
|
||||
@ -22,17 +22,21 @@ const BOTTOM_DESCRIPTION_MARGIN: i16 = 10;
|
||||
const LOADER_Y_OFFSET_TITLE: i16 = -10;
|
||||
const LOADER_Y_OFFSET_NO_TITLE: i16 = -20;
|
||||
|
||||
// Clippy was complaining about `very complex type used`
|
||||
type UpdateDescriptionFn<T, Error> = fn(&str) -> Result<T, Error>;
|
||||
|
||||
pub struct Progress<T>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
title: Child<Label<T>>,
|
||||
title: Option<Child<Label<T>>>,
|
||||
value: u16,
|
||||
loader_y_offset: i16,
|
||||
indeterminate: bool,
|
||||
description: Child<Paragraphs<Paragraph<T>>>,
|
||||
description_pad: Pad,
|
||||
update_description: fn(&str) -> Result<T, Error>,
|
||||
update_description: Option<UpdateDescriptionFn<T, Error>>,
|
||||
icon: Icon,
|
||||
}
|
||||
|
||||
impl<T> Progress<T>
|
||||
@ -41,14 +45,9 @@ where
|
||||
{
|
||||
const AREA: Rect = constant::screen();
|
||||
|
||||
pub fn new(
|
||||
title: T,
|
||||
indeterminate: bool,
|
||||
description: T,
|
||||
update_description: fn(&str) -> Result<T, Error>,
|
||||
) -> Self {
|
||||
pub fn new(indeterminate: bool, description: T) -> Self {
|
||||
Self {
|
||||
title: Child::new(Label::centered(title, theme::TEXT_BOLD)),
|
||||
title: None,
|
||||
value: 0,
|
||||
loader_y_offset: 0,
|
||||
indeterminate,
|
||||
@ -56,9 +55,42 @@ where
|
||||
Paragraph::new(&theme::TEXT_NORMAL, description).centered(),
|
||||
)),
|
||||
description_pad: Pad::with_background(theme::BG),
|
||||
update_description,
|
||||
update_description: None,
|
||||
icon: theme::ICON_TICK_FAT,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_title(mut self, title: T) -> Self {
|
||||
self.title = Some(Child::new(Label::centered(title, theme::TEXT_BOLD)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_update_description(
|
||||
mut self,
|
||||
update_description: UpdateDescriptionFn<T, Error>,
|
||||
) -> Self {
|
||||
self.update_description = Some(update_description);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_icon(mut self, icon: Icon) -> Self {
|
||||
self.icon = icon;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn request_paint(&self, ctx: &mut EventCtx) {
|
||||
if !animation_disabled() {
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value(&self) -> u16 {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn reached_max_value(&self) -> bool {
|
||||
self.value >= LOADER_MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for Progress<T>
|
||||
@ -78,11 +110,16 @@ where
|
||||
.filter(|c| *c == '\n')
|
||||
.count() as i16;
|
||||
|
||||
let (title, rest, loader_y_offset) = if self.title.inner().text().as_ref().is_empty() {
|
||||
(Rect::zero(), Self::AREA, LOADER_Y_OFFSET_NO_TITLE)
|
||||
let no_title_case = (Rect::zero(), Self::AREA, LOADER_Y_OFFSET_NO_TITLE);
|
||||
let (title, rest, loader_y_offset) = if let Some(self_title) = &self.title {
|
||||
if !self_title.inner().text().as_ref().is_empty() {
|
||||
let (title, rest) = Self::AREA.split_top(self_title.inner().max_size().y);
|
||||
(title, rest, LOADER_Y_OFFSET_TITLE)
|
||||
} else {
|
||||
no_title_case
|
||||
}
|
||||
} else {
|
||||
let (title, rest) = Self::AREA.split_top(self.title.inner().max_size().y);
|
||||
(title, rest, LOADER_Y_OFFSET_TITLE)
|
||||
no_title_case
|
||||
};
|
||||
|
||||
let (_loader, description) = rest.split_bottom(
|
||||
@ -96,18 +133,21 @@ where
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
self.title.event(ctx, event);
|
||||
self.description.event(ctx, event);
|
||||
|
||||
if let Event::Progress(new_value, new_description) = event {
|
||||
if mem::replace(&mut self.value, new_value) != new_value {
|
||||
if !animation_disabled() {
|
||||
ctx.request_paint();
|
||||
}
|
||||
self.request_paint(ctx);
|
||||
}
|
||||
if let Some(update_description) = self.update_description {
|
||||
self.description.mutate(ctx, |ctx, para| {
|
||||
// NOTE: not doing any change for empty new descriptions
|
||||
// (currently, there is no use-case for deleting the description)
|
||||
if !new_description.is_empty()
|
||||
&& para.inner_mut().content().as_ref() != new_description
|
||||
{
|
||||
let new_description = unwrap!((self.update_description)(new_description));
|
||||
let new_description = unwrap!((update_description)(new_description));
|
||||
para.inner_mut().update(new_description);
|
||||
para.change_page(0); // Recompute bounding box.
|
||||
ctx.request_paint();
|
||||
@ -135,7 +175,7 @@ where
|
||||
self.loader_y_offset,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
Some((theme::ICON_TICK_FAT, theme::FG)),
|
||||
Some((self.icon, theme::FG)),
|
||||
);
|
||||
}
|
||||
self.description_pad.paint();
|
||||
|
@ -1523,12 +1523,11 @@ extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Ma
|
||||
|
||||
// Description updates are received as &str and we need to provide a way to
|
||||
// convert them to StrBuffer.
|
||||
let obj = LayoutObj::new(Progress::new(
|
||||
title,
|
||||
indeterminate,
|
||||
description,
|
||||
StrBuffer::alloc,
|
||||
))?;
|
||||
let obj = LayoutObj::new(
|
||||
Progress::new(indeterminate, description)
|
||||
.with_title(title)
|
||||
.with_update_description(StrBuffer::alloc),
|
||||
)?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -1567,9 +1566,11 @@ extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut
|
||||
kwargs.get(Qstr::MP_QSTR_notification)?.try_into_option()?;
|
||||
let notification_level: u8 = kwargs.get_or(Qstr::MP_QSTR_notification_level, 0)?;
|
||||
let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?;
|
||||
let hold: bool = kwargs.get(Qstr::MP_QSTR_hold)?.try_into()?;
|
||||
|
||||
let notification = notification.map(|w| (w, notification_level));
|
||||
let obj = LayoutObj::new(Homescreen::new(label, notification))?;
|
||||
let loader_description = hold.then_some("Locking the device...".into());
|
||||
let obj = LayoutObj::new(Homescreen::new(label, notification, loader_description))?;
|
||||
if skip_first_paint {
|
||||
obj.skip_first_paint();
|
||||
}
|
||||
|
BIN
core/embed/rust/src/ui/model_tr/res/lock_small.toif
Normal file
BIN
core/embed/rust/src/ui/model_tr/res/lock_small.toif
Normal file
Binary file not shown.
@ -91,6 +91,7 @@ include_icon!(
|
||||
include_icon!(ICON_DEVICE_NAME, "model_tr/res/device_name.toif"); // 116*18
|
||||
include_icon!(ICON_EYE, "model_tr/res/eye_round.toif"); // 12*7
|
||||
include_icon!(ICON_LOCK, "model_tr/res/lock.toif"); // 10*10
|
||||
include_icon!(ICON_LOCK_SMALL, "model_tr/res/lock_small.toif"); // 6*7
|
||||
include_icon!(ICON_LOGO, "model_tr/res/logo_22_33.toif"); // 22*33
|
||||
include_icon!(ICON_LOGO_EMPTY, "model_tr/res/logo_22_33_empty.toif");
|
||||
include_icon!(
|
||||
|
@ -45,7 +45,7 @@ impl Trezor {
|
||||
Box::new(|_, m: protos::EthereumMessageSignature| {
|
||||
let signature = m.signature();
|
||||
if signature.len() != 65 {
|
||||
return Err(Error::MalformedSignature);
|
||||
return Err(Error::MalformedSignature)
|
||||
}
|
||||
let r = signature[0..32].try_into().unwrap();
|
||||
let s = signature[32..64].try_into().unwrap();
|
||||
|
Loading…
Reference in New Issue
Block a user