mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-06 12:51:21 +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
|
/// Component can also optionally return a message as a result of the
|
||||||
/// interaction.
|
/// 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.
|
/// No painting should be done in this phase.
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg>;
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg>;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ use crate::{
|
|||||||
component::{Component, Event, EventCtx, Never},
|
component::{Component, Event, EventCtx, Never},
|
||||||
constant,
|
constant,
|
||||||
display::{self, Color, Font, Icon},
|
display::{self, Color, Font, Icon},
|
||||||
|
event::PhysicalButton,
|
||||||
geometry::{Alignment2D, Offset, Point, Rect},
|
geometry::{Alignment2D, Offset, Point, Rect},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -20,6 +21,15 @@ pub enum ButtonPos {
|
|||||||
Right,
|
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>
|
pub struct Button<T>
|
||||||
where
|
where
|
||||||
T: StringType,
|
T: StringType,
|
||||||
|
@ -46,6 +46,9 @@ pub enum ButtonControllerMsg {
|
|||||||
Triggered(ButtonPos, bool),
|
Triggered(ButtonPos, bool),
|
||||||
/// Button was pressed and held for longer time (not released yet).
|
/// Button was pressed and held for longer time (not released yet).
|
||||||
LongPressed(ButtonPos),
|
LongPressed(ButtonPos),
|
||||||
|
/// Hold-to-confirm button was released prematurely - without triggering
|
||||||
|
/// LongPressed.
|
||||||
|
ReleasedWithoutLongPress(ButtonPos),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines what kind of button should be currently used.
|
/// Defines what kind of button should be currently used.
|
||||||
@ -186,10 +189,11 @@ where
|
|||||||
self.long_pressed_timer = None;
|
self.long_pressed_timer = None;
|
||||||
Some(ButtonControllerMsg::Triggered(self.pos, long_press))
|
Some(ButtonControllerMsg::Triggered(self.pos, long_press))
|
||||||
}
|
}
|
||||||
_ => {
|
ButtonType::HoldToConfirm(_) => {
|
||||||
self.hold_ended(ctx);
|
self.hold_ended(ctx);
|
||||||
None
|
Some(ButtonControllerMsg::ReleasedWithoutLongPress(self.pos))
|
||||||
}
|
}
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,6 +273,8 @@ where
|
|||||||
button_area: Rect,
|
button_area: Rect,
|
||||||
/// Handling optional ignoring of buttons after pressing the other button.
|
/// Handling optional ignoring of buttons after pressing the other button.
|
||||||
ignore_btn_delay: Option<IgnoreButtonDelay>,
|
ignore_btn_delay: Option<IgnoreButtonDelay>,
|
||||||
|
/// Whether to count with middle button
|
||||||
|
handle_middle_button: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ButtonController<T>
|
impl<T> ButtonController<T>
|
||||||
@ -276,6 +282,7 @@ where
|
|||||||
T: StringType,
|
T: StringType,
|
||||||
{
|
{
|
||||||
pub fn new(btn_layout: ButtonLayout<T>) -> Self {
|
pub fn new(btn_layout: ButtonLayout<T>) -> Self {
|
||||||
|
let handle_middle_button = btn_layout.btn_middle.is_some();
|
||||||
Self {
|
Self {
|
||||||
pad: Pad::with_background(theme::BG).with_clear(),
|
pad: Pad::with_background(theme::BG).with_clear(),
|
||||||
left_btn: ButtonContainer::new(ButtonPos::Left, btn_layout.btn_left),
|
left_btn: ButtonContainer::new(ButtonPos::Left, btn_layout.btn_left),
|
||||||
@ -284,6 +291,7 @@ where
|
|||||||
state: ButtonState::Nothing,
|
state: ButtonState::Nothing,
|
||||||
button_area: Rect::zero(),
|
button_area: Rect::zero(),
|
||||||
ignore_btn_delay: None,
|
ignore_btn_delay: None,
|
||||||
|
handle_middle_button,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,6 +304,7 @@ where
|
|||||||
|
|
||||||
/// Updating all the three buttons to the wanted states.
|
/// Updating all the three buttons to the wanted states.
|
||||||
pub fn set(&mut self, btn_layout: ButtonLayout<T>) {
|
pub fn set(&mut self, btn_layout: ButtonLayout<T>) {
|
||||||
|
self.handle_middle_button = btn_layout.btn_middle.is_some();
|
||||||
self.pad.clear();
|
self.pad.clear();
|
||||||
self.left_btn.set(btn_layout.btn_left, self.button_area);
|
self.left_btn.set(btn_layout.btn_left, self.button_area);
|
||||||
self.middle_btn.set(btn_layout.btn_middle, self.button_area);
|
self.middle_btn.set(btn_layout.btn_middle, self.button_area);
|
||||||
@ -471,13 +480,21 @@ where
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ↓ ↓
|
||||||
|
if self.handle_middle_button {
|
||||||
self.got_pressed(ctx, ButtonPos::Middle);
|
self.got_pressed(ctx, ButtonPos::Middle);
|
||||||
self.middle_hold_started(ctx);
|
self.middle_hold_started(ctx);
|
||||||
(
|
(
|
||||||
// ↓ ↓
|
|
||||||
ButtonState::BothDown,
|
ButtonState::BothDown,
|
||||||
Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)),
|
Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
self.got_pressed(ctx, b.into());
|
||||||
|
(
|
||||||
|
ButtonState::BothDown,
|
||||||
|
Some(ButtonControllerMsg::Pressed(b.into())),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => (self.state, None),
|
_ => (self.state, None),
|
||||||
},
|
},
|
||||||
@ -487,7 +504,14 @@ where
|
|||||||
ButtonEvent::ButtonReleased(b) => {
|
ButtonEvent::ButtonReleased(b) => {
|
||||||
self.middle_btn.hold_ended(ctx);
|
self.middle_btn.hold_ended(ctx);
|
||||||
// _ ↓ | ↓ _
|
// _ ↓ | ↓ _
|
||||||
|
if self.handle_middle_button {
|
||||||
(ButtonState::OneReleased(b), None)
|
(ButtonState::OneReleased(b), None)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
ButtonState::OneReleased(b),
|
||||||
|
Some(ButtonControllerMsg::Triggered(b.into(), false)),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => (self.state, None),
|
_ => (self.state, None),
|
||||||
},
|
},
|
||||||
@ -507,7 +531,14 @@ where
|
|||||||
ignore_btn_delay.make_button_clickable(ButtonPos::Left);
|
ignore_btn_delay.make_button_clickable(ButtonPos::Left);
|
||||||
ignore_btn_delay.make_button_clickable(ButtonPos::Right);
|
ignore_btn_delay.make_button_clickable(ButtonPos::Right);
|
||||||
}
|
}
|
||||||
|
if self.handle_middle_button {
|
||||||
(ButtonState::Nothing, self.middle_btn.maybe_trigger(ctx))
|
(ButtonState::Nothing, self.middle_btn.maybe_trigger(ctx))
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
ButtonState::Nothing,
|
||||||
|
Some(ButtonControllerMsg::Triggered(b.into(), false)),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => (self.state, None),
|
_ => (self.state, None),
|
||||||
},
|
},
|
||||||
|
@ -5,7 +5,7 @@ use crate::{
|
|||||||
component::{Child, Component, Event, EventCtx, Label},
|
component::{Child, Component, Event, EventCtx, Label},
|
||||||
constant::{HEIGHT, WIDTH},
|
constant::{HEIGHT, WIDTH},
|
||||||
display::{
|
display::{
|
||||||
rect_fill,
|
self, rect_fill,
|
||||||
toif::{Toif, ToifFormat},
|
toif::{Toif, ToifFormat},
|
||||||
Font, Icon,
|
Font, Icon,
|
||||||
},
|
},
|
||||||
@ -17,7 +17,7 @@ use crate::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
super::constant, common::display_center, theme, ButtonController, ButtonControllerMsg,
|
super::constant, common::display_center, theme, ButtonController, ButtonControllerMsg,
|
||||||
ButtonLayout, ButtonPos, CancelConfirmMsg,
|
ButtonLayout, ButtonPos, CancelConfirmMsg, LoaderMsg, ProgressLoader,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AREA: Rect = constant::screen();
|
const AREA: Rect = constant::screen();
|
||||||
@ -34,6 +34,8 @@ const NOTIFICATION_FONT: Font = Font::NORMAL;
|
|||||||
const NOTIFICATION_ICON: Icon = theme::ICON_WARNING;
|
const NOTIFICATION_ICON: Icon = theme::ICON_WARNING;
|
||||||
const COINJOIN_CORNER: Point = AREA.top_right().ofs(Offset::new(-2, 2));
|
const COINJOIN_CORNER: Point = AREA.top_right().ofs(Offset::new(-2, 2));
|
||||||
|
|
||||||
|
const HOLD_TO_LOCK_MS: u32 = 1000;
|
||||||
|
|
||||||
fn paint_default_image() {
|
fn paint_default_image() {
|
||||||
theme::ICON_LOGO.draw(
|
theme::ICON_LOGO.draw(
|
||||||
TOP_CENTER + Offset::y(LOGO_ICON_TOP_MARGIN),
|
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>
|
pub struct Homescreen<T>
|
||||||
where
|
where
|
||||||
T: StringType,
|
T: StringType,
|
||||||
@ -53,18 +61,31 @@ where
|
|||||||
notification: Option<(T, u8)>,
|
notification: Option<(T, u8)>,
|
||||||
/// Used for HTC functionality to lock device from homescreen
|
/// Used for HTC functionality to lock device from homescreen
|
||||||
invisible_buttons: Child<ButtonController<T>>,
|
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>
|
impl<T> Homescreen<T>
|
||||||
where
|
where
|
||||||
T: StringType + Clone,
|
T: StringType + Clone,
|
||||||
{
|
{
|
||||||
pub fn new(label: T, notification: Option<(T, u8)>) -> Self {
|
pub fn new(label: T, notification: Option<(T, u8)>, loader_description: Option<T>) -> Self {
|
||||||
let invisible_btn_layout = ButtonLayout::htc_none_htc("".into(), "".into());
|
// 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 {
|
Self {
|
||||||
label: Label::centered(label, theme::TEXT_BIG),
|
label: Label::centered(label, theme::TEXT_BIG),
|
||||||
notification,
|
notification,
|
||||||
invisible_buttons: Child::new(ButtonController::new(invisible_btn_layout)),
|
invisible_buttons: Child::new(ButtonController::new(invisible_btn_layout)),
|
||||||
|
loader,
|
||||||
|
show_loader: false,
|
||||||
|
current_screen: CurrentScreen::EmptyAtStart,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,19 +173,58 @@ where
|
|||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
self.label.place(LABEL_AREA);
|
self.label.place(LABEL_AREA);
|
||||||
|
self.loader.place(AREA);
|
||||||
bounds
|
bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
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::event_usb(self, ctx, event);
|
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) {
|
// 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(());
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
|
// 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
|
// Painting the homescreen image first, as the notification and label
|
||||||
// should be "on top of it"
|
// should be "on top of it"
|
||||||
self.paint_homescreen_image();
|
self.paint_homescreen_image();
|
||||||
@ -172,6 +232,7 @@ where
|
|||||||
self.paint_label();
|
self.paint_label();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Lockscreen<T>
|
pub struct Lockscreen<T>
|
||||||
where
|
where
|
||||||
|
@ -3,14 +3,15 @@ use crate::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
ui::{
|
ui::{
|
||||||
animation::Animation,
|
animation::Animation,
|
||||||
component::{Component, Event, EventCtx},
|
component::{Child, Component, Event, EventCtx},
|
||||||
display::{self, Color, Font},
|
constant,
|
||||||
|
display::{self, Color, Font, LOADER_MAX},
|
||||||
geometry::{Offset, Rect},
|
geometry::{Offset, Rect},
|
||||||
util::animation_disabled,
|
util::animation_disabled,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::theme;
|
use super::{theme, Progress};
|
||||||
|
|
||||||
pub const DEFAULT_DURATION_MS: u32 = 1000;
|
pub const DEFAULT_DURATION_MS: u32 = 1000;
|
||||||
pub const SHRINKING_DURATION_MS: u32 = 500;
|
pub const SHRINKING_DURATION_MS: u32 = 500;
|
||||||
@ -186,10 +187,9 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
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 let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||||
if self.is_animating() {
|
if self.is_animating() {
|
||||||
|
let now = Instant::now();
|
||||||
if self.is_completely_grown(now) {
|
if self.is_completely_grown(now) {
|
||||||
self.state = State::Grown;
|
self.state = State::Grown;
|
||||||
ctx.request_paint();
|
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
|
// DEBUG-ONLY SECTION BELOW
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
|
@ -22,7 +22,7 @@ pub use input_methods::{
|
|||||||
choice::{Choice, ChoiceFactory, ChoicePage},
|
choice::{Choice, ChoiceFactory, ChoicePage},
|
||||||
choice_item::ChoiceItem,
|
choice_item::ChoiceItem,
|
||||||
};
|
};
|
||||||
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet, ProgressLoader};
|
||||||
pub use result::ResultScreen;
|
pub use result::ResultScreen;
|
||||||
pub use welcome_screen::WelcomeScreen;
|
pub use welcome_screen::WelcomeScreen;
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use crate::{
|
|||||||
Child, Component, Event, EventCtx, Label, Never, Pad,
|
Child, Component, Event, EventCtx, Label, Never, Pad,
|
||||||
},
|
},
|
||||||
constant,
|
constant,
|
||||||
display::{self, Font},
|
display::{self, Font, Icon, LOADER_MAX},
|
||||||
geometry::Rect,
|
geometry::Rect,
|
||||||
util::animation_disabled,
|
util::animation_disabled,
|
||||||
},
|
},
|
||||||
@ -22,17 +22,21 @@ const BOTTOM_DESCRIPTION_MARGIN: i16 = 10;
|
|||||||
const LOADER_Y_OFFSET_TITLE: i16 = -10;
|
const LOADER_Y_OFFSET_TITLE: i16 = -10;
|
||||||
const LOADER_Y_OFFSET_NO_TITLE: i16 = -20;
|
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>
|
pub struct Progress<T>
|
||||||
where
|
where
|
||||||
T: StringType,
|
T: StringType,
|
||||||
{
|
{
|
||||||
title: Child<Label<T>>,
|
title: Option<Child<Label<T>>>,
|
||||||
value: u16,
|
value: u16,
|
||||||
loader_y_offset: i16,
|
loader_y_offset: i16,
|
||||||
indeterminate: bool,
|
indeterminate: bool,
|
||||||
description: Child<Paragraphs<Paragraph<T>>>,
|
description: Child<Paragraphs<Paragraph<T>>>,
|
||||||
description_pad: Pad,
|
description_pad: Pad,
|
||||||
update_description: fn(&str) -> Result<T, Error>,
|
update_description: Option<UpdateDescriptionFn<T, Error>>,
|
||||||
|
icon: Icon,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Progress<T>
|
impl<T> Progress<T>
|
||||||
@ -41,14 +45,9 @@ where
|
|||||||
{
|
{
|
||||||
const AREA: Rect = constant::screen();
|
const AREA: Rect = constant::screen();
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(indeterminate: bool, description: T) -> Self {
|
||||||
title: T,
|
|
||||||
indeterminate: bool,
|
|
||||||
description: T,
|
|
||||||
update_description: fn(&str) -> Result<T, Error>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
title: Child::new(Label::centered(title, theme::TEXT_BOLD)),
|
title: None,
|
||||||
value: 0,
|
value: 0,
|
||||||
loader_y_offset: 0,
|
loader_y_offset: 0,
|
||||||
indeterminate,
|
indeterminate,
|
||||||
@ -56,9 +55,42 @@ where
|
|||||||
Paragraph::new(&theme::TEXT_NORMAL, description).centered(),
|
Paragraph::new(&theme::TEXT_NORMAL, description).centered(),
|
||||||
)),
|
)),
|
||||||
description_pad: Pad::with_background(theme::BG),
|
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>
|
impl<T> Component for Progress<T>
|
||||||
@ -78,11 +110,16 @@ where
|
|||||||
.filter(|c| *c == '\n')
|
.filter(|c| *c == '\n')
|
||||||
.count() as i16;
|
.count() as i16;
|
||||||
|
|
||||||
let (title, rest, loader_y_offset) = if self.title.inner().text().as_ref().is_empty() {
|
let no_title_case = (Rect::zero(), Self::AREA, LOADER_Y_OFFSET_NO_TITLE);
|
||||||
(Rect::zero(), Self::AREA, LOADER_Y_OFFSET_NO_TITLE)
|
let (title, rest, loader_y_offset) = if let Some(self_title) = &self.title {
|
||||||
} else {
|
if !self_title.inner().text().as_ref().is_empty() {
|
||||||
let (title, rest) = Self::AREA.split_top(self.title.inner().max_size().y);
|
let (title, rest) = Self::AREA.split_top(self_title.inner().max_size().y);
|
||||||
(title, rest, LOADER_Y_OFFSET_TITLE)
|
(title, rest, LOADER_Y_OFFSET_TITLE)
|
||||||
|
} else {
|
||||||
|
no_title_case
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
no_title_case
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_loader, description) = rest.split_bottom(
|
let (_loader, description) = rest.split_bottom(
|
||||||
@ -96,18 +133,21 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.title.event(ctx, event);
|
||||||
|
self.description.event(ctx, event);
|
||||||
|
|
||||||
if let Event::Progress(new_value, new_description) = event {
|
if let Event::Progress(new_value, new_description) = event {
|
||||||
if mem::replace(&mut self.value, new_value) != new_value {
|
if mem::replace(&mut self.value, new_value) != new_value {
|
||||||
if !animation_disabled() {
|
self.request_paint(ctx);
|
||||||
ctx.request_paint();
|
|
||||||
}
|
}
|
||||||
|
if let Some(update_description) = self.update_description {
|
||||||
self.description.mutate(ctx, |ctx, para| {
|
self.description.mutate(ctx, |ctx, para| {
|
||||||
// NOTE: not doing any change for empty new descriptions
|
// NOTE: not doing any change for empty new descriptions
|
||||||
// (currently, there is no use-case for deleting the description)
|
// (currently, there is no use-case for deleting the description)
|
||||||
if !new_description.is_empty()
|
if !new_description.is_empty()
|
||||||
&& para.inner_mut().content().as_ref() != new_description
|
&& 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.inner_mut().update(new_description);
|
||||||
para.change_page(0); // Recompute bounding box.
|
para.change_page(0); // Recompute bounding box.
|
||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
@ -135,7 +175,7 @@ where
|
|||||||
self.loader_y_offset,
|
self.loader_y_offset,
|
||||||
theme::FG,
|
theme::FG,
|
||||||
theme::BG,
|
theme::BG,
|
||||||
Some((theme::ICON_TICK_FAT, theme::FG)),
|
Some((self.icon, theme::FG)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.description_pad.paint();
|
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
|
// Description updates are received as &str and we need to provide a way to
|
||||||
// convert them to StrBuffer.
|
// convert them to StrBuffer.
|
||||||
let obj = LayoutObj::new(Progress::new(
|
let obj = LayoutObj::new(
|
||||||
title,
|
Progress::new(indeterminate, description)
|
||||||
indeterminate,
|
.with_title(title)
|
||||||
description,
|
.with_update_description(StrBuffer::alloc),
|
||||||
StrBuffer::alloc,
|
)?;
|
||||||
))?;
|
|
||||||
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) }
|
||||||
@ -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()?;
|
kwargs.get(Qstr::MP_QSTR_notification)?.try_into_option()?;
|
||||||
let notification_level: u8 = kwargs.get_or(Qstr::MP_QSTR_notification_level, 0)?;
|
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 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 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 {
|
if skip_first_paint {
|
||||||
obj.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_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_EYE, "model_tr/res/eye_round.toif"); // 12*7
|
||||||
include_icon!(ICON_LOCK, "model_tr/res/lock.toif"); // 10*10
|
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, "model_tr/res/logo_22_33.toif"); // 22*33
|
||||||
include_icon!(ICON_LOGO_EMPTY, "model_tr/res/logo_22_33_empty.toif");
|
include_icon!(ICON_LOGO_EMPTY, "model_tr/res/logo_22_33_empty.toif");
|
||||||
include_icon!(
|
include_icon!(
|
||||||
|
@ -45,7 +45,7 @@ impl Trezor {
|
|||||||
Box::new(|_, m: protos::EthereumMessageSignature| {
|
Box::new(|_, m: protos::EthereumMessageSignature| {
|
||||||
let signature = m.signature();
|
let signature = m.signature();
|
||||||
if signature.len() != 65 {
|
if signature.len() != 65 {
|
||||||
return Err(Error::MalformedSignature);
|
return Err(Error::MalformedSignature)
|
||||||
}
|
}
|
||||||
let r = signature[0..32].try_into().unwrap();
|
let r = signature[0..32].try_into().unwrap();
|
||||||
let s = signature[32..64].try_into().unwrap();
|
let s = signature[32..64].try_into().unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user