1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-10 23:40:58 +00:00

fix(core/mercury): adjust swipe effect direction when animating transition through python

This commit is contained in:
tychovrahe 2024-06-18 12:50:01 +02:00 committed by matejcik
parent 2de8acc141
commit a4ff76e840
16 changed files with 162 additions and 44 deletions

View File

@ -0,0 +1 @@
[T3T1] Improve swipe behavior and animations

View File

@ -242,6 +242,7 @@ static void _librust_qstrs(void) {
MP_QSTR_flow_show_share_words; MP_QSTR_flow_show_share_words;
MP_QSTR_flow_warning_hi_prio; MP_QSTR_flow_warning_hi_prio;
MP_QSTR_get_language; MP_QSTR_get_language;
MP_QSTR_get_transition_out;
MP_QSTR_haptic_feedback__disable; MP_QSTR_haptic_feedback__disable;
MP_QSTR_haptic_feedback__enable; MP_QSTR_haptic_feedback__enable;
MP_QSTR_haptic_feedback__subtitle; MP_QSTR_haptic_feedback__subtitle;

View File

@ -212,6 +212,7 @@ where
pub struct Root<T> { pub struct Root<T> {
inner: Option<Child<T>>, inner: Option<Child<T>>,
marked_for_clear: bool, marked_for_clear: bool,
transition_out: Option<AttachType>,
} }
impl<T> Root<T> { impl<T> Root<T> {
@ -219,6 +220,7 @@ impl<T> Root<T> {
Self { Self {
inner: Some(Child::new(component)), inner: Some(Child::new(component)),
marked_for_clear: true, marked_for_clear: true,
transition_out: None,
} }
} }
@ -246,6 +248,10 @@ impl<T> Root<T> {
self.marked_for_clear = true; self.marked_for_clear = true;
} }
pub fn get_transition_out(&self) -> Option<AttachType> {
self.transition_out
}
pub fn delete(&mut self) { pub fn delete(&mut self) {
self.inner = None; self.inner = None;
} }
@ -270,6 +276,11 @@ where
assert!(paint_msg.is_none()); assert!(paint_msg.is_none());
assert!(dummy_ctx.timers.is_empty()); assert!(dummy_ctx.timers.is_empty());
} }
if let Some(t) = ctx.get_transition_out() {
self.transition_out = Some(t);
}
msg msg
} }
@ -528,6 +539,7 @@ pub struct EventCtx {
root_repaint_requested: bool, root_repaint_requested: bool,
swipe_disable_req: bool, swipe_disable_req: bool,
swipe_enable_req: bool, swipe_enable_req: bool,
transition_out: Option<AttachType>,
} }
impl EventCtx { impl EventCtx {
@ -557,6 +569,7 @@ impl EventCtx {
root_repaint_requested: false, root_repaint_requested: false,
swipe_disable_req: false, swipe_disable_req: false,
swipe_enable_req: false, swipe_enable_req: false,
transition_out: None,
} }
} }
@ -658,6 +671,7 @@ impl EventCtx {
self.root_repaint_requested = false; self.root_repaint_requested = false;
self.swipe_disable_req = false; self.swipe_disable_req = false;
self.swipe_enable_req = false; self.swipe_enable_req = false;
self.transition_out = None;
} }
fn register_timer(&mut self, token: TimerToken, deadline: Duration) { fn register_timer(&mut self, token: TimerToken, deadline: Duration) {
@ -680,4 +694,12 @@ impl EventCtx {
.unwrap_or(Self::STARTING_TIMER_TOKEN); .unwrap_or(Self::STARTING_TIMER_TOKEN);
token token
} }
pub fn set_transition_out(&mut self, attach_type: AttachType) {
self.transition_out = Some(attach_type);
}
pub fn get_transition_out(&self) -> Option<AttachType> {
self.transition_out
}
} }

View File

@ -5,7 +5,7 @@ use crate::ui::{
shape::Renderer, shape::Renderer,
}; };
#[derive(Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq, ToPrimitive, FromPrimitive)]
#[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))] #[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))]
pub enum SwipeDirection { pub enum SwipeDirection {
Up, Up,

View File

@ -5,7 +5,7 @@ use crate::{
base::AttachType, swipe_detect::SwipeSettings, Component, Event, EventCtx, SwipeDetect, base::AttachType, swipe_detect::SwipeSettings, Component, Event, EventCtx, SwipeDetect,
SwipeDetectMsg, SwipeDirection, SwipeDetectMsg, SwipeDirection,
}, },
event::SwipeEvent, event::{SwipeEvent, TouchEvent},
flow::{base::Decision, FlowMsg, FlowState, FlowStore}, flow::{base::Decision, FlowMsg, FlowState, FlowStore},
geometry::Rect, geometry::Rect,
shape::Renderer, shape::Renderer,
@ -107,6 +107,7 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
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 mut decision: Decision<Q> = Decision::Nothing; let mut decision: Decision<Q> = Decision::Nothing;
let mut return_transition: AttachType = AttachType::Initial;
let mut attach = false; let mut attach = false;
@ -139,6 +140,8 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
decision = self.handle_swipe_child(ctx, dir); decision = self.handle_swipe_child(ctx, dir);
} }
return_transition = AttachType::Swipe(dir);
let states_num = self.internal_pages; let states_num = self.internal_pages;
if states_num > 0 { if states_num > 0 {
if config.has_horizontal_pages() { if config.has_horizontal_pages() {
@ -230,6 +233,7 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
None None
} }
Decision::Return(msg) => { Decision::Return(msg) => {
ctx.set_transition_out(return_transition);
self.swipe.reset(); self.swipe.reset();
self.allow_swipe = true; self.allow_swipe = true;
Some(msg) Some(msg)

View File

@ -2,6 +2,7 @@ use core::{
cell::RefCell, cell::RefCell,
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
}; };
use num_traits::{FromPrimitive, ToPrimitive};
use crate::{ use crate::{
error::Error, error::Error,
@ -9,7 +10,7 @@ use crate::{
micropython::{ micropython::{
buffer::StrBuffer, buffer::StrBuffer,
gc::Gc, gc::Gc,
macros::{obj_dict, obj_fn_1, obj_fn_2, obj_fn_var, obj_map, obj_type}, macros::{obj_dict, obj_fn_1, obj_fn_2, obj_fn_3, obj_fn_var, obj_map, obj_type},
map::Map, map::Map,
obj::{Obj, ObjBase}, obj::{Obj, ObjBase},
qstr::Qstr, qstr::Qstr,
@ -32,9 +33,33 @@ use crate::ui::{display::Color, shape::render_on_display};
#[cfg(feature = "button")] #[cfg(feature = "button")]
use crate::ui::event::ButtonEvent; use crate::ui::event::ButtonEvent;
#[cfg(feature = "touch")]
use crate::ui::event::TouchEvent;
use crate::ui::event::USBEvent; 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(),
#[cfg(feature = "touch")]
Self::Swipe(dir) => 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)?,
));
}
#[allow(unreachable_code)]
Err(Error::TypeError)
}
}
/// Conversion trait implemented by components that know how to convert their /// Conversion trait implemented by components that know how to convert their
/// message values into MicroPython `Obj`s. /// message values into MicroPython `Obj`s.
@ -57,6 +82,7 @@ pub trait ObjComponent: MaybeTrace {
fn obj_skip_paint(&mut self) {} fn obj_skip_paint(&mut self) {}
fn obj_request_clear(&mut self) {} fn obj_request_clear(&mut self) {}
fn obj_delete(&mut self) {} fn obj_delete(&mut self) {}
fn obj_get_transition_out(&self) -> Result<Obj, Error>;
} }
impl<T> ObjComponent for Root<T> impl<T> ObjComponent for Root<T>
@ -112,6 +138,14 @@ where
fn obj_delete(&mut self) { fn obj_delete(&mut self) {
self.delete() self.delete()
} }
fn obj_get_transition_out(&self) -> Result<Obj, Error> {
if let Some(msg) = self.get_transition_out() {
Ok(msg.to_obj())
} else {
Ok(Obj::const_none())
}
}
} }
/// `LayoutObj` is a GC-allocated object exported to MicroPython, with type /// `LayoutObj` is a GC-allocated object exported to MicroPython, with type
@ -278,6 +312,14 @@ impl LayoutObj {
} }
} }
fn obj_get_transition_out(&self) -> Result<Obj, Error> {
let inner = &mut *self.inner.borrow_mut();
// Get transition out result
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
unsafe { Gc::as_mut(&mut inner.root) }.obj_get_transition_out()
}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
fn obj_bounds(&self) { fn obj_bounds(&self) {
use crate::ui::display; use crate::ui::display;
@ -299,7 +341,7 @@ impl LayoutObj {
static TYPE: Type = obj_type! { static TYPE: Type = obj_type! {
name: Qstr::MP_QSTR_LayoutObj, name: Qstr::MP_QSTR_LayoutObj,
locals: &obj_dict!(obj_map! { locals: &obj_dict!(obj_map! {
Qstr::MP_QSTR_attach_timer_fn => obj_fn_2!(ui_layout_attach_timer_fn).as_obj(), Qstr::MP_QSTR_attach_timer_fn => obj_fn_3!(ui_layout_attach_timer_fn).as_obj(),
Qstr::MP_QSTR_touch_event => obj_fn_var!(4, 4, ui_layout_touch_event).as_obj(), Qstr::MP_QSTR_touch_event => obj_fn_var!(4, 4, ui_layout_touch_event).as_obj(),
Qstr::MP_QSTR_button_event => obj_fn_var!(3, 3, ui_layout_button_event).as_obj(), Qstr::MP_QSTR_button_event => obj_fn_var!(3, 3, ui_layout_button_event).as_obj(),
Qstr::MP_QSTR_progress_event => obj_fn_var!(3, 3, ui_layout_progress_event).as_obj(), Qstr::MP_QSTR_progress_event => obj_fn_var!(3, 3, ui_layout_progress_event).as_obj(),
@ -312,6 +354,7 @@ impl LayoutObj {
Qstr::MP_QSTR___del__ => obj_fn_1!(ui_layout_delete).as_obj(), Qstr::MP_QSTR___del__ => obj_fn_1!(ui_layout_delete).as_obj(),
Qstr::MP_QSTR_page_count => obj_fn_1!(ui_layout_page_count).as_obj(), Qstr::MP_QSTR_page_count => obj_fn_1!(ui_layout_page_count).as_obj(),
Qstr::MP_QSTR_button_request => obj_fn_1!(ui_layout_button_request).as_obj(), Qstr::MP_QSTR_button_request => obj_fn_1!(ui_layout_button_request).as_obj(),
Qstr::MP_QSTR_get_transition_out => obj_fn_1!(ui_layout_get_transition_out).as_obj(),
}), }),
}; };
&TYPE &TYPE
@ -378,11 +421,12 @@ impl TryFrom<Never> for Obj {
} }
} }
extern "C" fn ui_layout_attach_timer_fn(this: Obj, timer_fn: Obj) -> Obj { extern "C" fn ui_layout_attach_timer_fn(this: Obj, timer_fn: Obj, attach_type: Obj) -> Obj {
let block = || { let block = || {
let this: Gc<LayoutObj> = this.try_into()?; let this: Gc<LayoutObj> = this.try_into()?;
this.obj_set_timer_fn(timer_fn); this.obj_set_timer_fn(timer_fn);
let msg = this.obj_event(Event::Attach(AttachType::Initial))?;
let msg = this.obj_event(Event::Attach(AttachType::try_from_obj(attach_type)?))?;
assert!(msg == Obj::const_none()); assert!(msg == Obj::const_none());
Ok(Obj::const_none()) Ok(Obj::const_none())
}; };
@ -510,6 +554,14 @@ extern "C" fn ui_layout_button_request(this: Obj) -> Obj {
unsafe { util::try_or_raise(block) } unsafe { util::try_or_raise(block) }
} }
extern "C" fn ui_layout_get_transition_out(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
this.obj_get_transition_out()
};
unsafe { util::try_or_raise(block) }
}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
#[no_mangle] #[no_mangle]
pub extern "C" fn ui_debug_layout_type() -> &'static Type { pub extern "C" fn ui_debug_layout_type() -> &'static Type {

View File

@ -45,6 +45,9 @@ impl AttachAnimation {
); );
match attach_type { match attach_type {
Some(AttachType::Initial) => {
Offset::lerp(Offset::new(0, -20), Offset::zero(), value.eval(t))
}
Some(AttachType::Swipe(dir)) => match dir { Some(AttachType::Swipe(dir)) => match dir {
SwipeDirection::Up => { SwipeDirection::Up => {
Offset::lerp(Offset::new(0, 20), Offset::zero(), value.eval(t)) Offset::lerp(Offset::new(0, 20), Offset::zero(), value.eval(t))
@ -66,7 +69,8 @@ impl AttachAnimation {
pareen::constant(1.0), pareen::constant(1.0),
); );
match attach_type { match attach_type {
Some(AttachType::Swipe(SwipeDirection::Up)) Some(AttachType::Initial)
| Some(AttachType::Swipe(SwipeDirection::Up))
| Some(AttachType::Swipe(SwipeDirection::Down)) => {} | Some(AttachType::Swipe(SwipeDirection::Down)) => {}
_ => { _ => {
return 255; return 255;
@ -89,9 +93,9 @@ pub struct SwipeContent<T> {
bounds: Rect, bounds: Rect,
progress: i16, progress: i16,
dir: SwipeDirection, dir: SwipeDirection,
normal: Option<AttachType>,
attach_animation: AttachAnimation, attach_animation: AttachAnimation,
attach_type: Option<AttachType>, attach_type: Option<AttachType>,
show_attach_anim: bool,
} }
impl<T: Component> SwipeContent<T> { impl<T: Component> SwipeContent<T> {
@ -101,17 +105,15 @@ impl<T: Component> SwipeContent<T> {
bounds: Rect::zero(), bounds: Rect::zero(),
progress: 0, progress: 0,
dir: SwipeDirection::Up, dir: SwipeDirection::Up,
normal: Some(AttachType::Swipe(SwipeDirection::Down)),
attach_animation: AttachAnimation::default(), attach_animation: AttachAnimation::default(),
attach_type: None, attach_type: None,
show_attach_anim: true,
} }
} }
pub fn with_normal_attach(self, attach_type: Option<AttachType>) -> Self { pub fn with_no_attach_anim(mut self) -> Self {
Self { self.show_attach_anim = false;
normal: attach_type, self
..self
}
} }
pub fn inner(&self) -> &T { pub fn inner(&self) -> &T {
@ -130,9 +132,7 @@ impl<T: Component> Component for SwipeContent<T> {
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Attach(attach_type) = event { if let Event::Attach(attach_type) = event {
self.progress = 0; self.progress = 0;
if let AttachType::Initial = attach_type { if self.show_attach_anim {
self.attach_type = self.normal;
} else {
self.attach_type = Some(attach_type); self.attach_type = Some(attach_type);
} }
self.attach_animation.reset(); self.attach_animation.reset();

View File

@ -1,6 +1,6 @@
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, SwipeDetect, SwipeDetectMsg}, component::{base::AttachType, Component, Event, EventCtx, SwipeDetect, SwipeDetectMsg},
event::SwipeEvent, event::{SwipeEvent},
flow::Swipable, flow::Swipable,
geometry::Rect, geometry::Rect,
shape::Renderer, shape::Renderer,
@ -49,7 +49,8 @@ impl<T: Swipable + Component> Component for SwipeUpScreen<T> {
.swipe .swipe
.event(ctx, event, self.content.get_swipe_config()) .event(ctx, event, self.content.get_swipe_config())
{ {
Some(SwipeDetectMsg::Trigger(_)) => { Some(SwipeDetectMsg::Trigger(dir)) => {
ctx.set_transition_out(AttachType::Swipe(dir));
return Some(SwipeUpScreenMsg::Swiped); return Some(SwipeUpScreenMsg::Swiped);
} }
Some(SwipeDetectMsg::Move(dir, progress)) => { Some(SwipeDetectMsg::Move(dir, progress)) => {

View File

@ -20,7 +20,7 @@ use crate::{
ui::{ ui::{
backlight::BACKLIGHT_LEVELS_OBJ, backlight::BACKLIGHT_LEVELS_OBJ,
component::{ component::{
base::{AttachType, ComponentExt}, base::ComponentExt,
connect::Connect, connect::Connect,
swipe_detect::SwipeSettings, swipe_detect::SwipeSettings,
text::{ text::{
@ -811,7 +811,7 @@ extern "C" fn new_show_success(n_args: usize, args: *const Obj, kwargs: *mut Map
let content = StatusScreen::new_success(); let content = StatusScreen::new_success();
let obj = LayoutObj::new(SwipeUpScreen::new( let obj = LayoutObj::new(SwipeUpScreen::new(
Frame::left_aligned(title, SwipeContent::new(content).with_normal_attach(None)) Frame::left_aligned(title, SwipeContent::new(content).with_no_attach_anim())
.with_footer(TR::instructions__swipe_up.into(), description) .with_footer(TR::instructions__swipe_up.into(), description)
.with_swipe(SwipeDirection::Up, SwipeSettings::default()), .with_swipe(SwipeDirection::Up, SwipeSettings::default()),
))?; ))?;
@ -1070,11 +1070,7 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M
.with_done_offset(theme::CHECKLIST_DONE_OFFSET); .with_done_offset(theme::CHECKLIST_DONE_OFFSET);
let obj = LayoutObj::new(SwipeUpScreen::new( let obj = LayoutObj::new(SwipeUpScreen::new(
Frame::left_aligned( Frame::left_aligned(title, SwipeContent::new(checklist_content))
title,
SwipeContent::new(checklist_content)
.with_normal_attach(Some(AttachType::Swipe(SwipeDirection::Up))),
)
.with_footer(TR::instructions__swipe_up.into(), None) .with_footer(TR::instructions__swipe_up.into(), None)
.with_swipe(SwipeDirection::Up, SwipeSettings::default()), .with_swipe(SwipeDirection::Up, SwipeSettings::default()),
))?; ))?;
@ -1317,12 +1313,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// ///
/// T = TypeVar("T") /// T = TypeVar("T")
/// ///
/// class AttachType:
/// ...
///
/// class LayoutObj(Generic[T]): /// class LayoutObj(Generic[T]):
/// """Representation of a Rust-based layout object. /// """Representation of a Rust-based layout object.
/// see `trezor::ui::layout::obj::LayoutObj`. /// see `trezor::ui::layout::obj::LayoutObj`.
/// """ /// """
/// ///
/// def attach_timer_fn(self, fn: Callable[[int, int], None]) -> None: /// def attach_timer_fn(self, fn: Callable[[int, int], None], attach_type: AttachType | None) -> None:
/// """Attach a timer setter function. /// """Attach a timer setter function.
/// ///
/// The layout object can call the timer setter with two arguments, /// The layout object can call the timer setter with two arguments,
@ -1379,6 +1378,9 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def page_count(self) -> int: /// def page_count(self) -> int:
/// """Return the number of pages in the layout object.""" /// """Return the number of pages in the layout object."""
/// ///
/// def get_transition_out(self) -> AttachType:
/// """Return the transition type."""
///
/// def __del__(self) -> None: /// def __del__(self) -> None:
/// """Calls drop on contents of the root component.""" /// """Calls drop on contents of the root component."""
/// ///

View File

@ -1625,12 +1625,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// ///
/// T = TypeVar("T") /// T = TypeVar("T")
/// ///
/// class AttachType:
/// ...
///
/// class LayoutObj(Generic[T]): /// class LayoutObj(Generic[T]):
/// """Representation of a Rust-based layout object. /// """Representation of a Rust-based layout object.
/// see `trezor::ui::layout::obj::LayoutObj`. /// see `trezor::ui::layout::obj::LayoutObj`.
/// """ /// """
/// ///
/// def attach_timer_fn(self, fn: Callable[[int, int], None]) -> None: /// def attach_timer_fn(self, fn: Callable[[int, int], None], attach_type: AttachType | None) -> None:
/// """Attach a timer setter function. /// """Attach a timer setter function.
/// ///
/// The layout object can call the timer setter with two arguments, /// The layout object can call the timer setter with two arguments,
@ -1690,6 +1693,9 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def button_request(self) -> tuple[int, str] | None: /// def button_request(self) -> tuple[int, str] | None:
/// """Return (code, type) of button request made during the last event or timer pass.""" /// """Return (code, type) of button request made during the last event or timer pass."""
/// ///
/// def get_transition_out(self) -> AttachType:
/// """Return the transition type."""
///
/// def __del__(self) -> None: /// def __del__(self) -> None:
/// """Calls drop on contents of the root component.""" /// """Calls drop on contents of the root component."""
/// ///

View File

@ -3,12 +3,17 @@ from trezor import utils
T = TypeVar("T") T = TypeVar("T")
# rust/src/ui/model_mercury/layout.rs
class AttachType:
...
# rust/src/ui/model_mercury/layout.rs # rust/src/ui/model_mercury/layout.rs
class LayoutObj(Generic[T]): class LayoutObj(Generic[T]):
"""Representation of a Rust-based layout object. """Representation of a Rust-based layout object.
see `trezor::ui::layout::obj::LayoutObj`. see `trezor::ui::layout::obj::LayoutObj`.
""" """
def attach_timer_fn(self, fn: Callable[[int, int], None]) -> None: def attach_timer_fn(self, fn: Callable[[int, int], None], attach_type: AttachType | None) -> None:
"""Attach a timer setter function. """Attach a timer setter function.
The layout object can call the timer setter with two arguments, The layout object can call the timer setter with two arguments,
`token` and `deadline`. When `deadline` is reached, the layout object `token` and `deadline`. When `deadline` is reached, the layout object
@ -49,6 +54,8 @@ class LayoutObj(Generic[T]):
"""Paint bounds of individual components on screen.""" """Paint bounds of individual components on screen."""
def page_count(self) -> int: def page_count(self) -> int:
"""Return the number of pages in the layout object.""" """Return the number of pages in the layout object."""
def get_transition_out(self) -> AttachType:
"""Return the transition type."""
def __del__(self) -> None: def __del__(self) -> None:
"""Calls drop on contents of the root component.""" """Calls drop on contents of the root component."""
@ -1083,12 +1090,17 @@ from trezor import utils
T = TypeVar("T") T = TypeVar("T")
# rust/src/ui/model_tt/layout.rs
class AttachType:
...
# rust/src/ui/model_tt/layout.rs # rust/src/ui/model_tt/layout.rs
class LayoutObj(Generic[T]): class LayoutObj(Generic[T]):
"""Representation of a Rust-based layout object. """Representation of a Rust-based layout object.
see `trezor::ui::layout::obj::LayoutObj`. see `trezor::ui::layout::obj::LayoutObj`.
""" """
def attach_timer_fn(self, fn: Callable[[int, int], None]) -> None: def attach_timer_fn(self, fn: Callable[[int, int], None], attach_type: AttachType | None) -> None:
"""Attach a timer setter function. """Attach a timer setter function.
The layout object can call the timer setter with two arguments, The layout object can call the timer setter with two arguments,
`token` and `deadline`. When `deadline` is reached, the layout object `token` and `deadline`. When `deadline` is reached, the layout object
@ -1131,6 +1143,8 @@ class LayoutObj(Generic[T]):
"""Return the number of pages in the layout object.""" """Return the number of pages in the layout object."""
def button_request(self) -> tuple[int, str] | None: def button_request(self) -> tuple[int, str] | None:
"""Return (code, type) of button request made during the last event or timer pass.""" """Return (code, type) of button request made during the last event or timer pass."""
def get_transition_out(self) -> AttachType:
"""Return the transition type."""
def __del__(self) -> None: def __del__(self) -> None:
"""Calls drop on contents of the root component.""" """Calls drop on contents of the root component."""

View File

@ -9,7 +9,7 @@ from trezorui2 import BacklightLevels
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Generic, TypeVar from typing import Generic, TypeVar
from trezorui2 import UiResult # noqa: F401 from trezorui2 import AttachType, UiResult # noqa: F401
T = TypeVar("T") T = TypeVar("T")
@ -34,6 +34,9 @@ layout_chan = loop.chan()
# allow only one alert at a time to avoid alerts overlapping # allow only one alert at a time to avoid alerts overlapping
_alert_in_progress = False _alert_in_progress = False
# storing last transition type, so that next layout can continue nicely
LAST_TRANSITION_OUT: AttachType | None = None
# in debug mode, display an indicator in top right corner # in debug mode, display an indicator in top right corner
if __debug__: if __debug__:
@ -131,6 +134,12 @@ class Layout(Generic[T]):
raised, usually from some of the child components. raised, usually from some of the child components.
""" """
def finalize(self) -> None:
"""
Called when the layout is done. Usually overridden to allow cleanup or storing context.
"""
pass
async def __iter__(self) -> T: async def __iter__(self) -> T:
""" """
Run the layout and wait until it completes. Returns the result value. Run the layout and wait until it completes. Returns the result value.
@ -159,6 +168,8 @@ class Layout(Generic[T]):
except Result as result: except Result as result:
# Result exception was raised, this means this layout is complete. # Result exception was raised, this means this layout is complete.
value = result.value value = result.value
finally:
self.finalize()
return value return value
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@ -36,7 +36,7 @@ class RustLayout(ui.Layout):
self.br_chan = loop.chan() self.br_chan = loop.chan()
self.layout = layout self.layout = layout
self.timer = loop.Timer() self.timer = loop.Timer()
self.layout.attach_timer_fn(self.set_timer) self.layout.attach_timer_fn(self.set_timer, ui.LAST_TRANSITION_OUT)
self._send_button_request() self._send_button_request()
self.backlight_level = ui.BacklightLevels.NORMAL self.backlight_level = ui.BacklightLevels.NORMAL
@ -191,6 +191,7 @@ class RustLayout(ui.Layout):
) )
def _first_paint(self) -> None: def _first_paint(self) -> None:
ui.backlight_fade(ui.BacklightLevels.NONE) ui.backlight_fade(ui.BacklightLevels.NONE)
self._paint() self._paint()
@ -213,7 +214,7 @@ class RustLayout(ui.Layout):
notify_layout_change(self, event_id) notify_layout_change(self, event_id)
# Turn the brightness on again. # Fade brightness to desired level
ui.backlight_fade(self.backlight_level) ui.backlight_fade(self.backlight_level)
def handle_input_and_rendering(self) -> loop.Task: def handle_input_and_rendering(self) -> loop.Task:
@ -260,13 +261,16 @@ class RustLayout(ui.Layout):
br_code, br_type = res br_code, br_type = res
self.br_chan.publish((br_code, br_type, self.layout.page_count())) self.br_chan.publish((br_code, br_type, self.layout.page_count()))
def finalize(self):
ui.LAST_TRANSITION_OUT = self.layout.get_transition_out()
def draw_simple(layout: Any) -> None: def draw_simple(layout: Any) -> None:
# Simple drawing not supported for layouts that set timers. # Simple drawing not supported for layouts that set timers.
def dummy_set_timer(token: int, deadline: int) -> None: def dummy_set_timer(token: int, deadline: int) -> None:
raise RuntimeError raise RuntimeError
layout.attach_timer_fn(dummy_set_timer) layout.attach_timer_fn(dummy_set_timer, None)
ui.backlight_fade(ui.BacklightLevels.DIM) ui.backlight_fade(ui.BacklightLevels.DIM)
layout.paint() layout.paint()
ui.refresh() ui.refresh()

View File

@ -35,7 +35,7 @@ class RustProgress:
): ):
self.layout = layout self.layout = layout
ui.backlight_fade(ui.BacklightLevels.DIM) ui.backlight_fade(ui.BacklightLevels.DIM)
self.layout.attach_timer_fn(self.set_timer) self.layout.attach_timer_fn(self.set_timer, None)
if self.layout.paint(): if self.layout.paint():
ui.refresh() ui.refresh()
ui.backlight_fade(ui.BacklightLevels.NORMAL) ui.backlight_fade(ui.BacklightLevels.NORMAL)

View File

@ -41,7 +41,7 @@ class RustLayout(LayoutParentType[T]):
self.br_chan = loop.chan() self.br_chan = loop.chan()
self.layout = layout self.layout = layout
self.timer = loop.Timer() self.timer = loop.Timer()
self.layout.attach_timer_fn(self.set_timer) self.layout.attach_timer_fn(self.set_timer, None)
self._send_button_request() self._send_button_request()
def __del__(self): def __del__(self):
@ -309,7 +309,7 @@ def draw_simple(layout: trezorui2.LayoutObj[Any]) -> None:
def dummy_set_timer(token: int, deadline: int) -> None: def dummy_set_timer(token: int, deadline: int) -> None:
raise RuntimeError raise RuntimeError
layout.attach_timer_fn(dummy_set_timer) layout.attach_timer_fn(dummy_set_timer, None)
layout.paint() layout.paint()
ui.refresh() ui.refresh()

View File

@ -42,7 +42,7 @@ class RustLayout(LayoutParentType[T]):
self.br_chan = loop.chan() self.br_chan = loop.chan()
self.layout = layout self.layout = layout
self.timer = loop.Timer() self.timer = loop.Timer()
self.layout.attach_timer_fn(self.set_timer) self.layout.attach_timer_fn(self.set_timer, None)
self._send_button_request() self._send_button_request()
self.backlight_level = ui.BacklightLevels.NORMAL self.backlight_level = ui.BacklightLevels.NORMAL
@ -272,7 +272,7 @@ def draw_simple(layout: trezorui2.LayoutObj[Any]) -> None:
def dummy_set_timer(token: int, deadline: int) -> None: def dummy_set_timer(token: int, deadline: int) -> None:
raise RuntimeError raise RuntimeError
layout.attach_timer_fn(dummy_set_timer) layout.attach_timer_fn(dummy_set_timer, None)
ui.backlight_fade(ui.BacklightLevels.DIM) ui.backlight_fade(ui.BacklightLevels.DIM)
layout.paint() layout.paint()
ui.refresh() ui.refresh()