1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-19 05:58:09 +00:00

fix(core/mercury): homescreen attach animation, resume animations after an interrupt from workflow

This commit is contained in:
tychovrahe 2024-07-01 13:22:12 +02:00 committed by TychoVrahe
parent 6b3aa768bd
commit 2994317dcd
14 changed files with 484 additions and 140 deletions

View File

@ -1 +1,2 @@
[T3T1] Smoothened screen transitions by removing backlight fading
[T3T1] Improved resuming of interrupted animations

View File

@ -6,11 +6,13 @@
static void _librust_qstrs(void) {
MP_QSTR_;
MP_QSTR_AttachType;
MP_QSTR_BacklightLevels;
MP_QSTR_CANCELLED;
MP_QSTR_CONFIRMED;
MP_QSTR_DIM;
MP_QSTR_INFO;
MP_QSTR_INITIAL;
MP_QSTR_LOW;
MP_QSTR_LayoutObj;
MP_QSTR_MAX;
@ -20,6 +22,11 @@ static void _librust_qstrs(void) {
MP_QSTR_MsgDef;
MP_QSTR_NONE;
MP_QSTR_NORMAL;
MP_QSTR_RESUME;
MP_QSTR_SWIPE_DOWN;
MP_QSTR_SWIPE_LEFT;
MP_QSTR_SWIPE_RIGHT;
MP_QSTR_SWIPE_UP;
MP_QSTR_TR;
MP_QSTR_TranslationsHeader;
MP_QSTR___del__;

View File

@ -348,7 +348,12 @@ where
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))]
pub enum AttachType {
/// Initial attach, redraw the whole screen
Initial,
/// The layout is already rendered on display, resume any animation
/// where we left off. The animation state is expected to be stored locally
/// in the given component.
Resume,
#[cfg(feature = "touch")]
Swipe(SwipeDirection),
}

View File

@ -5,6 +5,12 @@ use core::{
};
use num_traits::{FromPrimitive, ToPrimitive};
#[cfg(feature = "button")]
use crate::ui::event::ButtonEvent;
#[cfg(feature = "touch")]
use crate::ui::{component::SwipeDirection, event::TouchEvent};
#[cfg(feature = "new_rendering")]
use crate::ui::{display::Color, shape::render_on_display};
use crate::{
error::Error,
maybe_trace::MaybeTrace,
@ -15,52 +21,61 @@ use crate::{
map::Map,
obj::{Obj, ObjBase},
qstr::Qstr,
simple_type::SimpleTypeObj,
typ::Type,
util,
},
time::Duration,
ui::{
button_request::ButtonRequest,
component::{Component, Event, EventCtx, Never, TimerToken},
component::{base::AttachType, Component, Event, EventCtx, Never, TimerToken},
constant, display,
event::USBEvent,
geometry::Rect,
},
};
use crate::ui::component::base::AttachType;
#[cfg(feature = "new_rendering")]
use crate::ui::{display::Color, shape::render_on_display};
#[cfg(feature = "button")]
use crate::ui::event::ButtonEvent;
use crate::ui::event::USBEvent;
#[cfg(feature = "touch")]
use crate::ui::{component::SwipeDirection, event::TouchEvent};
impl AttachType {
fn to_obj(self) -> Obj {
match self {
Self::Initial => Obj::const_none(),
Self::Resume => 1u8.into(),
#[cfg(feature = "touch")]
Self::Swipe(dir) => dir.to_u8().into(),
Self::Swipe(dir) => (2u8 + unwrap!(dir.to_u8())).into(),
}
}
fn try_from_obj(obj: Obj) -> Result<Self, Error> {
if obj == Obj::const_none() {
return Ok(Self::Initial);
}
#[cfg(feature = "touch")]
{
let dir: u8 = obj.try_into()?;
return Ok(AttachType::Swipe(
SwipeDirection::from_u8(dir).ok_or(Error::TypeError)?,
));
let val: u8 = obj.try_into()?;
match val {
0 => Ok(Self::Initial),
1 => Ok(Self::Resume),
#[cfg(feature = "touch")]
2..=5 => Ok(Self::Swipe(
SwipeDirection::from_u8(val - 2).ok_or(Error::TypeError)?,
)),
_ => Err(Error::TypeError),
}
#[allow(unreachable_code)]
Err(Error::TypeError)
}
}
static ATTACH_TYPE: Type = obj_type! {
name: Qstr::MP_QSTR_AttachType,
locals: &obj_dict!(obj_map! {
Qstr::MP_QSTR_INITIAL => Obj::small_int(0u16),
Qstr::MP_QSTR_RESUME => Obj::small_int(1u16),
Qstr::MP_QSTR_SWIPE_UP => Obj::small_int(2u16),
Qstr::MP_QSTR_SWIPE_DOWN => Obj::small_int(3u16),
Qstr::MP_QSTR_SWIPE_LEFT => Obj::small_int(4u16),
Qstr::MP_QSTR_SWIPE_RIGHT => Obj::small_int(5u16),
}),
};
pub static ATTACH_TYPE_OBJ: SimpleTypeObj = SimpleTypeObj::new(&ATTACH_TYPE);
/// Conversion trait implemented by components that know how to convert their
/// message values into MicroPython `Obj`s.
pub trait ComponentMsgObj: Component {

View File

@ -16,7 +16,7 @@ use crate::{
};
use crate::ui::{
component::Label,
component::{base::AttachType, Label},
constant::{screen, HEIGHT, WIDTH},
lerp::Lerp,
model_mercury::{
@ -118,6 +118,122 @@ fn render_default_hs<'a>(target: &mut impl Renderer<'a>) {
.render(target);
}
struct HomescreenState {
attach: AttachAnimationState,
label: HideLabelAnimationState,
}
static mut HOMESCREEN_STATE: HomescreenState = HomescreenState {
attach: AttachAnimation::DEFAULT_STATE,
label: HideLabelAnimation::DEFAULT_STATE,
};
#[derive(Default, Clone)]
struct AttachAnimation {
pub timer: Stopwatch,
pub active: bool,
pub duration: Duration,
start_opacity: f32,
}
#[derive(Clone, Copy)]
struct AttachAnimationState {
opacity: u8,
}
impl AttachAnimation {
const DURATION_MS: u32 = 500;
pub const DEFAULT_STATE: AttachAnimationState = AttachAnimationState { opacity: 255 };
fn is_active(&self) -> bool {
if animation_disabled() {
return false;
}
self.timer.is_running_within(self.duration)
}
fn eval(&self) -> f32 {
if animation_disabled() {
return 1.0;
}
self.timer.elapsed().to_millis() as f32 / 1000.0
}
fn opacity(&self, t: f32) -> u8 {
if animation_disabled() {
return 255;
}
let f = pareen::constant(self.start_opacity)
.seq_ease_in_out(
0.0,
easer::functions::Linear,
self.duration.to_millis() as f32 / 1000.0,
pareen::constant(1.0),
)
.eval(t);
(f * 255.0) as u8
}
fn start(&mut self) {
self.active = true;
self.timer.start();
}
fn reset(&mut self) {
self.active = false;
self.timer = Stopwatch::new_stopped();
}
fn lazy_start(&mut self, ctx: &mut EventCtx, event: Event, resume: AttachAnimationState) {
match event {
Event::Attach(AttachType::Initial) => {
self.duration = Duration::from_millis(Self::DURATION_MS);
self.reset();
if !animation_disabled() {
ctx.request_anim_frame();
}
}
Event::Attach(AttachType::Resume) => {
let start_opacity = resume.opacity as f32 / 255.0;
let duration = start_opacity * Self::DURATION_MS as f32;
self.start_opacity = start_opacity;
self.duration = Duration::from_millis(duration as u32);
self.timer = Stopwatch::new_stopped();
self.active = resume.opacity < 255;
if !animation_disabled() {
ctx.request_anim_frame();
}
}
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
if !self.timer.is_running() {
self.start();
}
if self.is_active() {
ctx.request_anim_frame();
ctx.request_paint();
} else if self.active {
self.active = false;
ctx.request_anim_frame();
ctx.request_paint();
}
}
_ => {}
}
}
fn get_state(&self, t: f32) -> AttachAnimationState {
AttachAnimationState {
opacity: self.opacity(t),
}
}
}
struct HideLabelAnimation {
pub timer: Stopwatch,
token: TimerToken,
@ -125,9 +241,23 @@ struct HideLabelAnimation {
hidden: bool,
duration: Duration,
}
#[derive(Clone, Copy)]
struct HideLabelAnimationState {
animating: bool,
hidden: bool,
elapsed: u32,
}
impl HideLabelAnimation {
const HIDE_AFTER: Duration = Duration::from_millis(3000);
pub const DEFAULT_STATE: HideLabelAnimationState = HideLabelAnimationState {
animating: false,
hidden: false,
elapsed: 0,
};
fn new(label_width: i16) -> Self {
Self {
timer: Stopwatch::default(),
@ -146,6 +276,10 @@ impl HideLabelAnimation {
self.timer = Stopwatch::default();
}
fn elapsed(&self) -> Duration {
self.timer.elapsed()
}
fn change_dir(&mut self) {
let elapsed = self.timer.elapsed();
@ -191,53 +325,81 @@ impl HideLabelAnimation {
Offset::x(i16::lerp(-(label_width + 12), 0, pos))
}
fn process_event(&mut self, ctx: &mut EventCtx, event: Event) {
if let Event::Attach(_) = event {
ctx.request_anim_frame();
self.token = ctx.request_timer(Self::HIDE_AFTER);
}
if let Event::Timer(token) = event {
if token == self.token && !animation_disabled() {
self.timer.start();
fn process_event(&mut self, ctx: &mut EventCtx, event: Event, resume: HideLabelAnimationState) {
match event {
Event::Attach(AttachType::Initial) => {
ctx.request_anim_frame();
self.animating = true;
self.hidden = false;
self.token = ctx.request_timer(Self::HIDE_AFTER);
}
}
Event::Attach(AttachType::Resume) => {
self.hidden = resume.hidden;
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
if self.is_active() {
ctx.request_anim_frame();
ctx.request_paint();
} else if self.animating {
self.animating = false;
self.hidden = !self.hidden;
self.reset();
ctx.request_paint();
let start = Instant::now()
.checked_sub(Duration::from_millis(resume.elapsed))
.unwrap_or(Instant::now());
if !self.hidden {
self.animating = resume.animating;
if self.animating {
self.timer = Stopwatch::Running(start);
ctx.request_anim_frame();
} else {
self.timer = Stopwatch::new_stopped();
}
if !self.animating && !self.hidden {
self.token = ctx.request_timer(Self::HIDE_AFTER);
}
}
}
if let Event::Touch(TouchEvent::TouchStart(_)) = event {
if !self.animating {
if self.hidden {
self.timer.start();
self.animating = true;
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
if self.is_active() {
ctx.request_anim_frame();
ctx.request_paint();
} else {
self.token = ctx.request_timer(Self::HIDE_AFTER);
} else if self.animating {
self.animating = false;
self.hidden = !self.hidden;
self.reset();
ctx.request_paint();
if !self.hidden {
self.token = ctx.request_timer(Self::HIDE_AFTER);
}
}
} else if !self.hidden {
self.change_dir();
self.hidden = true;
ctx.request_anim_frame();
ctx.request_paint();
}
Event::Timer(token) => {
if token == self.token && !animation_disabled() {
self.timer.start();
ctx.request_anim_frame();
self.animating = true;
self.hidden = false;
}
}
Event::Touch(TouchEvent::TouchStart(_)) => {
if !self.animating {
if self.hidden {
self.timer.start();
self.animating = true;
ctx.request_anim_frame();
ctx.request_paint();
} else {
self.token = ctx.request_timer(Self::HIDE_AFTER);
}
} else if !self.hidden {
self.change_dir();
self.hidden = true;
ctx.request_anim_frame();
ctx.request_paint();
}
}
_ => {}
}
}
fn get_state(&self) -> HideLabelAnimationState {
HideLabelAnimationState {
animating: self.animating,
hidden: self.hidden,
elapsed: self.timer.elapsed().to_millis(),
}
}
}
@ -259,6 +421,7 @@ pub struct Homescreen {
hold_to_lock: bool,
loader: Loader,
delay: Option<TimerToken>,
attach_animation: AttachAnimation,
label_anim: HideLabelAnimation,
}
@ -296,6 +459,7 @@ impl Homescreen {
hold_to_lock,
loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
delay: None,
attach_animation: AttachAnimation::default(),
label_anim: HideLabelAnimation::new(label_width),
}
}
@ -395,9 +559,15 @@ impl Component for Homescreen {
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
// SAFETY: Single threaded access
let resume_attach = unsafe { HOMESCREEN_STATE.attach };
self.attach_animation.lazy_start(ctx, event, resume_attach);
Self::event_usb(self, ctx, event);
self.label_anim.process_event(ctx, event);
let resume_label = unsafe { HOMESCREEN_STATE.label };
self.label_anim.process_event(ctx, event, resume_label);
if self.hold_to_lock {
Self::event_hold(self, ctx, event).then_some(HomescreenMsg::Dismissed)
@ -414,7 +584,18 @@ impl Component for Homescreen {
if self.loader.is_animating() || self.loader.is_completely_grown(Instant::now()) {
self.render_loader(target);
} else {
shape::RawImage::new(AREA, self.bg_image.view()).render(target);
let t = self.attach_animation.eval();
let opacity = self.attach_animation.opacity(t);
if let Some(image) = self.image {
if let ImageInfo::Jpeg(_) = ImageInfo::parse(image) {
shape::JpegImage::new_image(AREA.center(), image)
.with_align(Alignment2D::CENTER)
.render(target);
}
} else {
render_default_hs(target);
}
let y_offset = self.label_anim.eval(self.label_width);
@ -437,6 +618,17 @@ impl Component for Homescreen {
if let Some(notif) = self.get_notification() {
render_notif(notif, NOTIFICATION_TOP, target);
}
shape::Bar::new(AREA)
.with_bg(Color::black())
.with_alpha(255 - opacity)
.render(target);
// SAFETY: Single threaded access
unsafe {
HOMESCREEN_STATE.attach = self.attach_animation.get_state(t);
HOMESCREEN_STATE.label = self.label_anim.get_state();
}
}
}
}
@ -449,13 +641,34 @@ impl crate::trace::Trace for Homescreen {
}
}
struct LockscreenState {
attach: AttachAnimationState,
overlay: LockscreenAnimState,
label: HideLabelAnimationState,
}
static mut LOCKSCREEN_STATE: LockscreenState = LockscreenState {
attach: AttachAnimation::DEFAULT_STATE,
overlay: LockscreenAnim::DEFAULT_STATE,
label: HideLabelAnimation::DEFAULT_STATE,
};
#[derive(Default)]
struct LockscreenAnim {
pub start: f32,
pub timer: Stopwatch,
}
#[derive(Clone, Copy)]
struct LockscreenAnimState {
angle: f32,
}
impl LockscreenAnim {
const DURATION_MS: u32 = 1500;
pub const DEFAULT_STATE: LockscreenAnimState = LockscreenAnimState { angle: 0.0 };
pub fn is_active(&self) -> bool {
true
}
@ -468,12 +681,44 @@ impl LockscreenAnim {
let t: f32 = self.timer.elapsed().to_millis() as f32 / 1000.0;
anim.eval(t)
self.start + anim.eval(t)
}
pub fn lazy_start(&mut self, ctx: &mut EventCtx, event: Event, resume: LockscreenAnimState) {
match event {
Event::Attach(AttachType::Initial) => {
self.start = 0.0;
if !animation_disabled() {
ctx.request_anim_frame();
}
}
Event::Attach(AttachType::Resume) => {
self.start = resume.angle;
if !animation_disabled() {
ctx.request_anim_frame();
}
}
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
if !animation_disabled() {
if !self.timer.is_running() {
self.timer.start();
}
ctx.request_anim_frame();
ctx.request_paint();
}
}
_ => {}
}
}
pub fn get_state(&self) -> LockscreenAnimState {
LockscreenAnimState { angle: self.eval() }
}
}
pub struct Lockscreen {
anim: LockscreenAnim,
attach_animation: AttachAnimation,
label: Label<'static>,
name_width: i16,
label_width: i16,
@ -512,6 +757,7 @@ impl Lockscreen {
Lockscreen {
anim: LockscreenAnim::default(),
attach_animation: AttachAnimation::default(),
label: Label::new(label, Alignment::Center, theme::TEXT_DEMIBOLD),
name_width,
label_width,
@ -535,21 +781,18 @@ impl Component for Lockscreen {
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Attach(_) = event {
ctx.request_anim_frame();
}
// SAFETY: Single threaded access
let resume_attach = unsafe { LOCKSCREEN_STATE.attach };
self.attach_animation.lazy_start(ctx, event, resume_attach);
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
if !animation_disabled() {
if !self.anim.timer.is_running() {
self.anim.timer.start();
}
ctx.request_anim_frame();
ctx.request_paint();
}
}
// SAFETY: Single threaded access
let resume_overlay = unsafe { LOCKSCREEN_STATE.overlay };
self.anim.lazy_start(ctx, event, resume_overlay);
self.label_anim.process_event(ctx, event);
// SAFETY: Single threaded access
let resume_label = unsafe { LOCKSCREEN_STATE.label };
self.label_anim.process_event(ctx, event, resume_label);
if let Event::Touch(TouchEvent::TouchEnd(_)) = event {
return Some(HomescreenMsg::Dismissed);
@ -569,7 +812,8 @@ impl Component for Lockscreen {
shape::RawImage::new(AREA, self.bg_image.view()).render(target);
cshape::UnlockOverlay::new(center, self.anim.eval()).render(target);
let overlay_rotation = self.anim.eval();
cshape::UnlockOverlay::new(center, overlay_rotation).render(target);
shape::Bar::new(AREA.split_top(OVERLAY_BORDER).0)
.with_bg(Color::black())
@ -651,6 +895,22 @@ impl Component for Lockscreen {
render_notif(notif, NOTIFICATION_LOCKSCREEN_TOP, target);
}
let t = self.attach_animation.eval();
let opacity = self.attach_animation.opacity(t);
shape::Bar::new(AREA)
.with_bg(Color::black())
.with_fg(Color::black())
.with_alpha(255 - opacity)
.render(target);
// SAFETY: Single threaded access
unsafe {
LOCKSCREEN_STATE.attach = self.attach_animation.get_state(t);
LOCKSCREEN_STATE.overlay = self.anim.get_state();
LOCKSCREEN_STATE.label = self.label_anim.get_state();
}
}
}

View File

@ -124,20 +124,28 @@ impl Loader {
}
}
pub fn progress(&self, now: Instant) -> Option<u16> {
pub fn progress_raw(&self, now: Instant) -> Option<u16> {
self.animation().map(|a| a.value(now))
}
pub fn progress(&self, now: Instant) -> Option<u16> {
if animation_disabled() {
self.progress_raw(now).map(|_a| display::LOADER_MIN)
} else {
self.progress_raw(now)
}
}
pub fn is_animating(&self) -> bool {
self.animation().is_some()
}
pub fn is_completely_grown(&self, now: Instant) -> bool {
matches!(self.progress(now), Some(display::LOADER_MAX))
matches!(self.progress_raw(now), Some(display::LOADER_MAX))
}
pub fn is_completely_shrunk(&self, now: Instant) -> bool {
matches!(self.progress(now), Some(display::LOADER_MIN))
matches!(self.progress_raw(now), Some(display::LOADER_MIN))
}
}

View File

@ -1,5 +1,16 @@
use core::{cmp::Ordering, convert::TryInto};
use super::{
component::{
AddressDetails, Bip39Input, Button, CancelConfirmMsg, CancelInfoConfirmMsg,
CoinJoinProgress, FidoConfirm, FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg,
Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard,
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, PromptScreen,
SelectWordCount, SelectWordCountMsg, SetBrightnessDialog, Slip39Input, StatusScreen,
SwipeUpScreen, SwipeUpScreenMsg, VerticalMenu, VerticalMenuChoiceMsg,
},
flow, theme,
};
use crate::{
error::{value_error, Error},
io::BinaryData,
@ -36,7 +47,7 @@ use crate::{
flow::Swipable,
geometry,
layout::{
obj::{ComponentMsgObj, LayoutObj},
obj::{ComponentMsgObj, LayoutObj, ATTACH_TYPE_OBJ},
result::{CANCELLED, CONFIRMED, INFO},
util::{upy_disable_animation, ConfirmBlob, PropsList},
},
@ -47,18 +58,6 @@ use crate::{
},
};
use super::{
component::{
AddressDetails, Bip39Input, Button, CancelConfirmMsg, CancelInfoConfirmMsg,
CoinJoinProgress, FidoConfirm, FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg,
Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard,
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, PromptScreen,
SelectWordCount, SelectWordCountMsg, SetBrightnessDialog, Slip39Input, StatusScreen,
SwipeUpScreen, SwipeUpScreenMsg, VerticalMenu, VerticalMenuChoiceMsg,
},
flow, theme,
};
impl TryFrom<CancelConfirmMsg> for Obj {
type Error = Error;
@ -1313,9 +1312,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
///
/// T = TypeVar("T")
///
/// class AttachType:
/// ...
///
/// class LayoutObj(Generic[T]):
/// """Representation of a Rust-based layout object.
/// see `trezor::ui::layout::obj::LayoutObj`.
@ -1905,4 +1901,14 @@ pub static mp_module_trezorui2: Module = obj_module! {
///
/// mock:global
Qstr::MP_QSTR_BacklightLevels => BACKLIGHT_LEVELS_OBJ.as_obj(),
/// class AttachType:
/// INITIAL: ClassVar[int]
/// RESUME: ClassVar[int]
/// SWIPE_UP: ClassVar[int]
/// SWIPE_DOWN: ClassVar[int]
/// SWIPE_LEFT: ClassVar[int]
/// SWIPE_RIGHT: ClassVar[int]
Qstr::MP_QSTR_AttachType => ATTACH_TYPE_OBJ.as_obj(),
};

View File

@ -2,6 +2,16 @@ use core::{cmp::Ordering, convert::TryInto};
use heapless::Vec;
use super::{
component::{
AddressDetails, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, CancelConfirmMsg,
CancelInfoConfirmMsg, CoinJoinProgress, ConfirmHomescreen, Flow, FlowPages, Frame,
Homescreen, Lockscreen, NumberInput, Page, PassphraseEntry, PinEntry, Progress,
ScrollableContent, ScrollableFrame, ShareWords, ShowMore, SimpleChoice, WordlistEntry,
WordlistType,
},
constant, theme,
};
use crate::{
error::Error,
maybe_trace::MaybeTrace,
@ -37,7 +47,7 @@ use crate::{
},
geometry,
layout::{
obj::{ComponentMsgObj, LayoutObj},
obj::{ComponentMsgObj, LayoutObj, ATTACH_TYPE_OBJ},
result::{CANCELLED, CONFIRMED, INFO},
util::{upy_disable_animation, ConfirmBlob},
},
@ -45,17 +55,6 @@ use crate::{
},
};
use super::{
component::{
AddressDetails, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, CancelConfirmMsg,
CancelInfoConfirmMsg, CoinJoinProgress, ConfirmHomescreen, Flow, FlowPages, Frame,
Homescreen, Lockscreen, NumberInput, Page, PassphraseEntry, PinEntry, Progress,
ScrollableContent, ScrollableFrame, ShareWords, ShowMore, SimpleChoice, WordlistEntry,
WordlistType,
},
constant, theme,
};
impl From<CancelConfirmMsg> for Obj {
fn from(value: CancelConfirmMsg) -> Self {
match value {
@ -2063,4 +2062,13 @@ pub static mp_module_trezorui2: Module = obj_module! {
///
/// mock:global
Qstr::MP_QSTR_BacklightLevels => BACKLIGHT_LEVELS_OBJ.as_obj(),
/// class AttachType:
/// INITIAL: ClassVar[int]
/// RESUME: ClassVar[int]
/// SWIPE_UP: ClassVar[int]
/// SWIPE_DOWN: ClassVar[int]
/// SWIPE_LEFT: ClassVar[int]
/// SWIPE_RIGHT: ClassVar[int]
Qstr::MP_QSTR_AttachType => ATTACH_TYPE_OBJ.as_obj(),
};

View File

@ -1,5 +1,17 @@
use core::{cmp::Ordering, convert::TryInto};
use super::{
component::{
AddressDetails, Bip39Input, Button, ButtonMsg, ButtonPage, ButtonStyleSheet,
CancelConfirmMsg, CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm,
FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput,
MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg,
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
SelectWordCount, SelectWordCountMsg, SelectWordMsg, SetBrightnessDialog, SimplePage,
Slip39Input,
},
theme,
};
use crate::{
error::{value_error, Error},
io::BinaryData,
@ -38,7 +50,7 @@ use crate::{
},
geometry,
layout::{
obj::{ComponentMsgObj, LayoutObj},
obj::{ComponentMsgObj, LayoutObj, ATTACH_TYPE_OBJ},
result::{CANCELLED, CONFIRMED, INFO},
util::{upy_disable_animation, ConfirmBlob, PropsList},
},
@ -46,19 +58,6 @@ use crate::{
},
};
use super::{
component::{
AddressDetails, Bip39Input, Button, ButtonMsg, ButtonPage, ButtonStyleSheet,
CancelConfirmMsg, CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm,
FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput,
MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg,
PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
SelectWordCount, SelectWordCountMsg, SelectWordMsg, SetBrightnessDialog, SimplePage,
Slip39Input,
},
theme,
};
impl TryFrom<CancelConfirmMsg> for Obj {
type Error = Error;
@ -1625,9 +1624,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
///
/// T = TypeVar("T")
///
/// class AttachType:
/// ...
///
/// class LayoutObj(Generic[T]):
/// """Representation of a Rust-based layout object.
/// see `trezor::ui::layout::obj::LayoutObj`.
@ -2153,6 +2149,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
///
/// mock:global
Qstr::MP_QSTR_BacklightLevels => BACKLIGHT_LEVELS_OBJ.as_obj(),
/// class AttachType:
/// INITIAL: ClassVar[int]
/// RESUME: ClassVar[int]
/// SWIPE_UP: ClassVar[int]
/// SWIPE_DOWN: ClassVar[int]
/// SWIPE_LEFT: ClassVar[int]
/// SWIPE_RIGHT: ClassVar[int]
Qstr::MP_QSTR_AttachType => ATTACH_TYPE_OBJ.as_obj(),
};
#[cfg(test)]

View File

@ -3,11 +3,6 @@ from trezor import utils
T = TypeVar("T")
# rust/src/ui/model_mercury/layout.rs
class AttachType:
...
# rust/src/ui/model_mercury/layout.rs
class LayoutObj(Generic[T]):
"""Representation of a Rust-based layout object.
@ -621,6 +616,16 @@ class BacklightLevels:
LOW: ClassVar[int]
DIM: ClassVar[int]
NONE: ClassVar[int]
# rust/src/ui/model_mercury/layout.rs
class AttachType:
INITIAL: ClassVar[int]
RESUME: ClassVar[int]
SWIPE_UP: ClassVar[int]
SWIPE_DOWN: ClassVar[int]
SWIPE_LEFT: ClassVar[int]
SWIPE_RIGHT: ClassVar[int]
CONFIRMED: UiResult
CANCELLED: UiResult
INFO: UiResult
@ -1091,15 +1096,20 @@ class BacklightLevels:
LOW: ClassVar[int]
DIM: ClassVar[int]
NONE: ClassVar[int]
# rust/src/ui/model_tr/layout.rs
class AttachType:
INITIAL: ClassVar[int]
RESUME: ClassVar[int]
SWIPE_UP: ClassVar[int]
SWIPE_DOWN: ClassVar[int]
SWIPE_LEFT: ClassVar[int]
SWIPE_RIGHT: ClassVar[int]
from trezor import utils
T = TypeVar("T")
# rust/src/ui/model_tt/layout.rs
class AttachType:
...
# rust/src/ui/model_tt/layout.rs
class LayoutObj(Generic[T]):
"""Representation of a Rust-based layout object.
@ -1642,3 +1652,13 @@ class BacklightLevels:
LOW: ClassVar[int]
DIM: ClassVar[int]
NONE: ClassVar[int]
# rust/src/ui/model_tt/layout.rs
class AttachType:
INITIAL: ClassVar[int]
RESUME: ClassVar[int]
SWIPE_UP: ClassVar[int]
SWIPE_DOWN: ClassVar[int]
SWIPE_LEFT: ClassVar[int]
SWIPE_RIGHT: ClassVar[int]

View File

@ -4,12 +4,12 @@ from trezorui import Display
from typing import TYPE_CHECKING, Any, Awaitable, Generator
from trezor import loop, utils
from trezorui2 import BacklightLevels
from trezorui2 import AttachType, BacklightLevels
if TYPE_CHECKING:
from typing import Generic, TypeVar
from trezorui2 import AttachType, UiResult # noqa: F401
from trezorui2 import UiResult # noqa: F401
T = TypeVar("T")

View File

@ -36,10 +36,13 @@ class RustLayout(ui.Layout):
self.br_chan = loop.chan()
self.layout = layout
self.timer = loop.Timer()
self.layout.attach_timer_fn(self.set_timer, ui.LAST_TRANSITION_OUT)
self._attach()
self._send_button_request()
self.backlight_level = ui.BacklightLevels.NORMAL
def _attach(self) -> None:
self.layout.attach_timer_fn(self.set_timer, ui.LAST_TRANSITION_OUT)
def __del__(self):
self.layout.__del__()

View File

@ -18,6 +18,12 @@ class HomescreenBase(RustLayout):
def __init__(self, layout: Any) -> None:
super().__init__(layout=layout)
def _attach(self) -> None:
if storage_cache.homescreen_shown is self.RENDER_INDICATOR:
self.layout.attach_timer_fn(self.set_timer, ui.AttachType.RESUME)
else:
self.layout.attach_timer_fn(self.set_timer, ui.LAST_TRANSITION_OUT)
def _paint(self) -> None:
if self.layout.paint():
ui.refresh()

View File

@ -15334,7 +15334,7 @@
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "eb98ca2a5a32eafe3f900478beb4258e67c896baec5ee44b6bed4bb0b0f6b606",
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "503270a6bef3f40595776027e60c23297cbb043e9d0d68e0aef1812f75ade20e",
"T3T1_cs_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "0541c248157f4c0f10dcaa338aea083ec15a7293c865a5de3d9cb13df8ecb41e",
"T3T1_cs_test_lock.py::test_hold_to_lock": "dad08e836117c018d5c91d42a51195ab3784b9109bf8feb88c9b16b868951b56",
"T3T1_cs_test_lock.py::test_hold_to_lock": "1824eb2d9867a82fb99174bac6ef8a906a0d6bf11381e0939091fee8a9c10b45",
"T3T1_cs_test_passphrase_mercury.py::test_cycle_through_last_character": "3ad09c8d05197cb0ac1627c792bebda14e1cd031c0c1414c9ace143c60f79fae",
"T3T1_cs_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "956e3bb892ca76d13da3bb45556dc19789d4fbfdde1052c4c954139f503f0c81",
"T3T1_cs_test_passphrase_mercury.py::test_passphrase_delete": "6f496d80c2ab52faf30679ceffd8915926d098be42f47123aaf18345e23baeeb",
@ -15383,7 +15383,7 @@
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "93332879c26051be67399b47ce6135e729783ac35be14bba8a18f4bd7fad0163",
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "1c2c503e0ecf55359e7dc7800b190d71b01c6dc953d65494e9dd6940f40e4341",
"T3T1_de_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "84895c70dcbeacd69227294e245f4a4aadd89aa25a33c2af70e40e88251fbc54",
"T3T1_de_test_lock.py::test_hold_to_lock": "dfadb3325a3352d33b4048c414c2c1a5eca35b9bfb1412d0cde926e36b184ef0",
"T3T1_de_test_lock.py::test_hold_to_lock": "b2136906b71a65f5e949f79e80f381b44a5b87e59f67f23ee22767abb588dfcf",
"T3T1_de_test_passphrase_mercury.py::test_cycle_through_last_character": "63c08446669db6dd58a639b5fd2e18ca75f8f0309dd3588dbd7fe8d96352a2b4",
"T3T1_de_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "bf9d0f3b2d019fff0af115958eed8b6aed3b8794b10a18ce02dd9dfb35037009",
"T3T1_de_test_passphrase_mercury.py::test_passphrase_delete": "79efacad4f082f5e54879a81dcac263a01a7583a8d645e5f2102f61e308671bc",
@ -15432,7 +15432,7 @@
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "37ee82e8643b9e226a332f8d8f644d38efa11abee4d5fa01cc7aeef259efdf48",
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "abd96d32b92c8ef5c8ac4cac59ea78f623f7aef14392f98b6608c4ff62eabf19",
"T3T1_en_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "6ee94a1eb6437c83569d05695e0c224398f8b074d2e1a4c8a8b5b3496af87f55",
"T3T1_en_test_lock.py::test_hold_to_lock": "1bd07afb3ce583aa8b7afd2a1b9b72c64f81e12ecc3382fef8306a90e8f33624",
"T3T1_en_test_lock.py::test_hold_to_lock": "0dd53e59ef70d6b20ad463f16ae28ad18472cd7e26e5bf61eae78b05cb395009",
"T3T1_en_test_passphrase_mercury.py::test_cycle_through_last_character": "77fe6f8bfb1ddc6a8516913c2ebc669e6b988e6ef9ea6846ef6d94e921fa65af",
"T3T1_en_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "c5540d1c33265bcd397b8a72fe949eef9b514c708fb3bbc2d3d2aef804cc8a23",
"T3T1_en_test_passphrase_mercury.py::test_passphrase_delete": "ce2aaa6bc1c26c65a4278ebd3e23dac4adf81fb71dd72af685db34ba766c3935",
@ -15481,7 +15481,7 @@
"T3T1_es_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "1f535bab5bdf06f820fb00b278cd8e10a686c9162f4d16e068e2da01e5d01281",
"T3T1_es_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "fb5f271ecf82ce97bdf7a10c0a8b6d158a356c0a4e5a50ce114c464695e2f3d3",
"T3T1_es_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "3f28ed90d81f57c3c92467a8b98d4bb4362cf0fefbb7a54a79fe7653dc4a624f",
"T3T1_es_test_lock.py::test_hold_to_lock": "78ee06d95dc3e030a2720c608a3ee90e1af5fdaf02869823089eab12cbcdf997",
"T3T1_es_test_lock.py::test_hold_to_lock": "22121f35ab8ef5850185109c604bf540c3a7691b7a61c46ddbebc78e36e59ae4",
"T3T1_es_test_passphrase_mercury.py::test_cycle_through_last_character": "3fc39e02f19cf7626398c62eeba26f2e36ee6449e3390283fa96c016ea85d58e",
"T3T1_es_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "83116b38b2709f164d512a3e53dc36b2fc65cd635f8cc5dd49a7e4d01b52b554",
"T3T1_es_test_passphrase_mercury.py::test_passphrase_delete": "7e4da3874efd61f9a37ecd276a3f104d15b511a075702de130ffc1b60ce96a2c",
@ -15530,7 +15530,7 @@
"T3T1_fr_test_backup_slip39_custom.py::test_backup_slip39_custom[1of1]": "46f84e9b2b88d4108ec7e7ae7000d8e3e9fe4fa4642955ba3e243cee5ee8caf4",
"T3T1_fr_test_backup_slip39_custom.py::test_backup_slip39_custom[2of3]": "6708cae42ad5c8ebfc6fcf11dfa9ef86e761075ecd071172c3798f2c5f6ab359",
"T3T1_fr_test_backup_slip39_custom.py::test_backup_slip39_custom[5of5]": "10dce76a0863c78385b97f7c1aeca8d75fb5b15a13ff8a4e630bd8862be4d890",
"T3T1_fr_test_lock.py::test_hold_to_lock": "b0722dd84e823c408bea43a2a1a7ce00a310fa02c08e848cb2b5102337c0ee6d",
"T3T1_fr_test_lock.py::test_hold_to_lock": "31961b0cba116a3a786e5758e4753f703fb9b8499d4c6cc7bffc8a162de70e82",
"T3T1_fr_test_passphrase_mercury.py::test_cycle_through_last_character": "6a74ce7c04eb4b26b79b91ee0f1580e1296ebf25750f5b237451e84afabb69c5",
"T3T1_fr_test_passphrase_mercury.py::test_passphrase_click_same_button_many_times": "ce9ad70bbb739a124b7fb0e039a7c63954b04609f869c549b4ed0dbaab590c7f",
"T3T1_fr_test_passphrase_mercury.py::test_passphrase_delete": "b4ee17ffa5f236746430884da9fafa69379ddb5c88383d517ce7e5cc1d88e7cc",