diff --git a/core/embed/rust/src/ui/component/base.rs b/core/embed/rust/src/ui/component/base.rs index b6999c768..441d5ab50 100644 --- a/core/embed/rust/src/ui/component/base.rs +++ b/core/embed/rust/src/ui/component/base.rs @@ -400,6 +400,42 @@ impl TimerToken { } } +#[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))] +pub struct Timer(Option); + +impl Timer { + /// Create a new timer. + pub const fn new() -> Self { + Self(None) + } + + /// Start this timer for a given duration. + /// + /// Requests the internal timer token to be scheduled to `duration` from + /// now. If the timer was already running, its token is rescheduled. + pub fn start(&mut self, ctx: &mut EventCtx, duration: Duration) { + let token = self.0.get_or_insert_with(|| ctx.next_timer_token()); + ctx.register_timer(*token, duration); + } + + /// Stop the timer. + /// + /// Does not affect scheduling, only clears the internal timer token. This + /// means that _some_ scheduled task might keep running, but this timer + /// will not trigger when that task expires. + pub fn stop(&mut self) { + self.0 = None; + } + + /// Check if the timer has expired. + /// + /// Returns `true` if the given event is a timer event and the token matches + /// the internal token of this timer. + pub fn is_expired(&self, event: Event) -> bool { + matches!(event, Event::Timer(token) if self.0 == Some(token)) + } +} + pub struct EventCtx { timers: Vec<(TimerToken, Duration), { Self::MAX_TIMERS }>, next_token: u32, @@ -464,13 +500,6 @@ impl EventCtx { self.paint_requested = true; } - /// Request a timer event to be delivered after `duration` elapses. - pub fn request_timer(&mut self, duration: Duration) -> TimerToken { - let token = self.next_timer_token(); - self.register_timer(token, duration); - token - } - /// Request an animation frame timer to fire as soon as possible. pub fn request_anim_frame(&mut self) { if !self.anim_frame_scheduled { @@ -479,6 +508,10 @@ impl EventCtx { } } + pub fn is_anim_frame(event: Event) -> bool { + matches!(event, Event::Timer(token) if token == Self::ANIM_FRAME_TIMER) + } + pub fn request_repaint_root(&mut self) { self.root_repaint_requested = true; } diff --git a/core/embed/rust/src/ui/component/marquee.rs b/core/embed/rust/src/ui/component/marquee.rs index d6a91386c..2fa598be5 100644 --- a/core/embed/rust/src/ui/component/marquee.rs +++ b/core/embed/rust/src/ui/component/marquee.rs @@ -3,7 +3,7 @@ use crate::{ time::{Duration, Instant}, ui::{ animation::Animation, - component::{Component, Event, EventCtx, Never, TimerToken}, + component::{Component, Event, EventCtx, Never, Timer}, display::{self, Color, Font}, geometry::{Offset, Rect}, shape::{self, Renderer}, @@ -24,7 +24,7 @@ enum State { pub struct Marquee { area: Rect, - pause_token: Option, + pause_timer: Timer, min_offset: i16, max_offset: i16, state: State, @@ -40,7 +40,7 @@ impl Marquee { pub fn new(text: TString<'static>, font: Font, fg: Color, bg: Color) -> Self { Self { area: Rect::zero(), - pause_token: None, + pause_timer: Timer::new(), min_offset: 0, max_offset: 0, state: State::Initial, @@ -154,53 +154,50 @@ impl Component for Marquee { let now = Instant::now(); - if let Event::Timer(token) = event { - if self.pause_token == Some(token) { - match self.state { - State::PauseLeft => { - let anim = - Animation::new(self.max_offset, self.min_offset, self.duration, now); - self.state = State::Right(anim); - } - State::PauseRight => { - let anim = - Animation::new(self.min_offset, self.max_offset, self.duration, now); - self.state = State::Left(anim); - } - _ => {} + if self.pause_timer.is_expired(event) { + match self.state { + State::PauseLeft => { + let anim = Animation::new(self.max_offset, self.min_offset, self.duration, now); + self.state = State::Right(anim); + } + State::PauseRight => { + let anim = Animation::new(self.min_offset, self.max_offset, self.duration, now); + self.state = State::Left(anim); } + _ => {} + } + // We have something to paint, so request to be painted in the next pass. + ctx.request_paint(); + // There is further progress in the animation, request an animation frame event. + ctx.request_anim_frame(); + } + + if EventCtx::is_anim_frame(event) { + if self.is_animating() { // We have something to paint, so request to be painted in the next pass. ctx.request_paint(); - // There is further progress in the animation, request an animation frame event. + // There is further progress in the animation, request an animation frame + // event. ctx.request_anim_frame(); } - if token == EventCtx::ANIM_FRAME_TIMER { - if self.is_animating() { - // We have something to paint, so request to be painted in the next pass. - ctx.request_paint(); - // There is further progress in the animation, request an animation frame - // event. - ctx.request_anim_frame(); - } - - match self.state { - State::Right(_) => { - if self.is_at_right(now) { - self.pause_token = Some(ctx.request_timer(self.pause)); - self.state = State::PauseRight; - } + match self.state { + State::Right(_) => { + if self.is_at_right(now) { + self.pause_timer.start(ctx, self.pause); + self.state = State::PauseRight; } - State::Left(_) => { - if self.is_at_left(now) { - self.pause_token = Some(ctx.request_timer(self.pause)); - self.state = State::PauseLeft; - } + } + State::Left(_) => { + if self.is_at_left(now) { + self.pause_timer.start(ctx, self.pause); + self.state = State::PauseLeft; } - _ => {} } + _ => {} } } + None } diff --git a/core/embed/rust/src/ui/component/mod.rs b/core/embed/rust/src/ui/component/mod.rs index 7cc7b8de7..680ef8b73 100644 --- a/core/embed/rust/src/ui/component/mod.rs +++ b/core/embed/rust/src/ui/component/mod.rs @@ -27,7 +27,7 @@ pub mod text; pub mod timeout; pub use bar::Bar; -pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, TimerToken}; +pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Timer}; pub use border::Border; pub use button_request::{ButtonRequestExt, SendButtonRequest}; #[cfg(all(feature = "jpeg", feature = "ui_image_buffer", feature = "micropython"))] diff --git a/core/embed/rust/src/ui/component/timeout.rs b/core/embed/rust/src/ui/component/timeout.rs index bd0466c10..534b7d5d0 100644 --- a/core/embed/rust/src/ui/component/timeout.rs +++ b/core/embed/rust/src/ui/component/timeout.rs @@ -1,23 +1,22 @@ use crate::{ time::Duration, ui::{ - component::{Component, Event, EventCtx, TimerToken}, + component::{Component, Event, EventCtx, Timer}, geometry::Rect, shape::Renderer, }, }; -#[derive(Clone)] pub struct Timeout { time_ms: u32, - timer: Option, + timer: Timer, } impl Timeout { pub fn new(time_ms: u32) -> Self { Self { time_ms, - timer: None, + timer: Timer::new(), } } } @@ -30,19 +29,10 @@ impl Component for Timeout { } fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - match event { - // Set up timer. - Event::Attach(_) => { - self.timer = Some(ctx.request_timer(Duration::from_millis(self.time_ms))); - None - } - // Fire. - Event::Timer(token) if Some(token) == self.timer => { - self.timer = None; - Some(()) - } - _ => None, + if matches!(event, Event::Attach(_)) { + self.timer.start(ctx, Duration::from_millis(self.time_ms)); } + self.timer.is_expired(event).then_some(()) } fn paint(&mut self) {} diff --git a/core/embed/rust/src/ui/layout/obj.rs b/core/embed/rust/src/ui/layout/obj.rs index 87f57769b..779bb975a 100644 --- a/core/embed/rust/src/ui/layout/obj.rs +++ b/core/embed/rust/src/ui/layout/obj.rs @@ -28,7 +28,7 @@ use crate::{ time::Duration, ui::{ button_request::ButtonRequest, - component::{base::AttachType, Component, Event, EventCtx, Never, TimerToken}, + component::{base::{AttachType, TimerToken}, Component, Event, EventCtx, Never}, constant, display, event::USBEvent, geometry::Rect, diff --git a/core/embed/rust/src/ui/model_mercury/component/address_details.rs b/core/embed/rust/src/ui/model_mercury/component/address_details.rs index d5149f4d6..bd42a1af3 100644 --- a/core/embed/rust/src/ui/model_mercury/component/address_details.rs +++ b/core/embed/rust/src/ui/model_mercury/component/address_details.rs @@ -21,7 +21,6 @@ use super::{theme, Frame, FrameMsg}; const MAX_XPUBS: usize = 16; -#[derive(Clone)] pub struct AddressDetails { details: Frame>>, xpub_view: Frame>>, diff --git a/core/embed/rust/src/ui/model_mercury/component/button.rs b/core/embed/rust/src/ui/model_mercury/component/button.rs index 7f0a7446d..bb7126fbd 100644 --- a/core/embed/rust/src/ui/model_mercury/component/button.rs +++ b/core/embed/rust/src/ui/model_mercury/component/button.rs @@ -5,13 +5,12 @@ use crate::{ time::Duration, ui::{ component::{ - Component, ComponentExt, Event, EventCtx, FixedHeightBar, MsgMap, Split, TimerToken, + Component, ComponentExt, Event, EventCtx, FixedHeightBar, MsgMap, Split, Timer, }, display::{self, toif::Icon, Color, Font}, event::TouchEvent, geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect}, - shape, - shape::Renderer, + shape::{self, Renderer}, }, }; @@ -24,7 +23,6 @@ pub enum ButtonMsg { LongPressed, } -#[derive(Clone)] pub struct Button { area: Rect, touch_expand: Option, @@ -34,7 +32,7 @@ pub struct Button { radius: Option, state: State, long_press: Option, - long_timer: Option, + long_timer: Timer, haptic: bool, } @@ -54,7 +52,7 @@ impl Button { radius: None, state: State::Initial, long_press: None, - long_timer: None, + long_timer: Timer::new(), haptic: true, } } @@ -348,7 +346,7 @@ impl Component for Button { } self.set(ctx, State::Pressed); if let Some(duration) = self.long_press { - self.long_timer = Some(ctx.request_timer(duration)); + self.long_timer.start(ctx, duration); } return Some(ButtonMsg::Pressed); } @@ -380,13 +378,13 @@ impl Component for Button { State::Pressed => { // Touch finished outside our area. self.set(ctx, State::Initial); - self.long_timer = None; + self.long_timer.stop(); return Some(ButtonMsg::Released); } _ => { // Touch finished outside our area. self.set(ctx, State::Initial); - self.long_timer = None; + self.long_timer.stop(); } } } @@ -398,28 +396,25 @@ impl Component for Button { State::Pressed => { // Touch aborted self.set(ctx, State::Initial); - self.long_timer = None; + self.long_timer.stop(); return Some(ButtonMsg::Released); } _ => { // Irrelevant touch abort self.set(ctx, State::Initial); - self.long_timer = None; + self.long_timer.stop(); } } } - Event::Timer(token) => { - if self.long_timer == Some(token) { - self.long_timer = None; - if matches!(self.state, State::Pressed) { - #[cfg(feature = "haptic")] - if self.haptic { - play(HapticEffect::ButtonPress); - } - self.set(ctx, State::Initial); - return Some(ButtonMsg::LongPressed); + Event::Timer(_) if self.long_timer.is_expired(event) => { + if matches!(self.state, State::Pressed) { + #[cfg(feature = "haptic")] + if self.haptic { + play(HapticEffect::ButtonPress); } + self.set(ctx, State::Initial); + return Some(ButtonMsg::LongPressed); } } _ => {} diff --git a/core/embed/rust/src/ui/model_mercury/component/frame.rs b/core/embed/rust/src/ui/model_mercury/component/frame.rs index cc78b6de1..ab913ba65 100644 --- a/core/embed/rust/src/ui/model_mercury/component/frame.rs +++ b/core/embed/rust/src/ui/model_mercury/component/frame.rs @@ -80,7 +80,6 @@ impl HorizontalSwipe { } } -#[derive(Clone)] pub struct Frame { border: Insets, bounds: Rect, diff --git a/core/embed/rust/src/ui/model_mercury/component/header.rs b/core/embed/rust/src/ui/model_mercury/component/header.rs index c8c7e7464..3800fb106 100644 --- a/core/embed/rust/src/ui/model_mercury/component/header.rs +++ b/core/embed/rust/src/ui/model_mercury/component/header.rs @@ -61,7 +61,7 @@ impl AttachAnimation { } const BUTTON_EXPAND_BORDER: i16 = 32; -#[derive(Clone)] + pub struct Header { area: Rect, title: Label<'static>, diff --git a/core/embed/rust/src/ui/model_mercury/component/hold_to_confirm.rs b/core/embed/rust/src/ui/model_mercury/component/hold_to_confirm.rs index 258882d9e..f0f2b145e 100644 --- a/core/embed/rust/src/ui/model_mercury/component/hold_to_confirm.rs +++ b/core/embed/rust/src/ui/model_mercury/component/hold_to_confirm.rs @@ -163,7 +163,6 @@ impl HoldToConfirmAnim { /// Component requesting a hold to confirm action from a user. Most typically /// embedded as a content of a Frame. -#[derive(Clone)] pub struct HoldToConfirm { title: Label<'static>, area: Rect, diff --git a/core/embed/rust/src/ui/model_mercury/component/homescreen.rs b/core/embed/rust/src/ui/model_mercury/component/homescreen.rs index c025b0b33..d37367399 100644 --- a/core/embed/rust/src/ui/model_mercury/component/homescreen.rs +++ b/core/embed/rust/src/ui/model_mercury/component/homescreen.rs @@ -5,7 +5,7 @@ use crate::{ translations::TR, trezorhal::usb::usb_configured, ui::{ - component::{Component, Event, EventCtx, TimerToken}, + component::{Component, Event, EventCtx, Timer}, display::{image::ImageInfo, Color, Font}, event::{TouchEvent, USBEvent}, geometry::{Alignment, Alignment2D, Offset, Point, Rect}, @@ -235,8 +235,8 @@ impl AttachAnimation { } struct HideLabelAnimation { - pub timer: Stopwatch, - token: TimerToken, + pub stopwatch: Stopwatch, + timer: Timer, animating: bool, hidden: bool, duration: Duration, @@ -260,8 +260,8 @@ impl HideLabelAnimation { fn new(label_width: i16) -> Self { Self { - timer: Stopwatch::default(), - token: TimerToken::INVALID, + stopwatch: Stopwatch::default(), + timer: Timer::new(), animating: false, hidden: false, duration: Duration::from_millis((label_width as u32 * 300) / 120), @@ -269,19 +269,19 @@ impl HideLabelAnimation { } fn is_active(&self) -> bool { - self.timer.is_running_within(self.duration) + self.stopwatch.is_running_within(self.duration) } fn reset(&mut self) { - self.timer = Stopwatch::default(); + self.stopwatch = Stopwatch::default(); } fn elapsed(&self) -> Duration { - self.timer.elapsed() + self.stopwatch.elapsed() } fn change_dir(&mut self) { - let elapsed = self.timer.elapsed(); + let elapsed = self.stopwatch.elapsed(); let start = self .duration @@ -289,9 +289,9 @@ impl HideLabelAnimation { .and_then(|e| Instant::now().checked_sub(e)); if let Some(start) = start { - self.timer = Stopwatch::Running(start); + self.stopwatch = Stopwatch::Running(start); } else { - self.timer = Stopwatch::new_started(); + self.stopwatch = Stopwatch::new_started(); } } @@ -300,7 +300,7 @@ impl HideLabelAnimation { return Offset::zero(); } - let t = self.timer.elapsed().to_millis() as f32 / 1000.0; + let t = self.stopwatch.elapsed().to_millis() as f32 / 1000.0; let pos = if self.hidden { pareen::constant(0.0) @@ -329,7 +329,7 @@ impl HideLabelAnimation { match event { Event::Attach(AttachType::Initial) => { ctx.request_anim_frame(); - self.token = ctx.request_timer(Self::HIDE_AFTER); + self.timer.start(ctx, Self::HIDE_AFTER); } Event::Attach(AttachType::Resume) => { self.hidden = resume.hidden; @@ -341,13 +341,13 @@ impl HideLabelAnimation { self.animating = resume.animating; if self.animating { - self.timer = Stopwatch::Running(start); + self.stopwatch = Stopwatch::Running(start); ctx.request_anim_frame(); } else { - self.timer = Stopwatch::new_stopped(); + self.stopwatch = Stopwatch::new_stopped(); } if !self.animating && !self.hidden { - self.token = ctx.request_timer(Self::HIDE_AFTER); + self.timer.start(ctx, Self::HIDE_AFTER); } } Event::Timer(EventCtx::ANIM_FRAME_TIMER) => { @@ -361,28 +361,26 @@ impl HideLabelAnimation { ctx.request_paint(); if !self.hidden { - self.token = ctx.request_timer(Self::HIDE_AFTER); + self.timer.start(ctx, Self::HIDE_AFTER); } } } - Event::Timer(token) => { - if token == self.token && !animation_disabled() { - self.timer.start(); - ctx.request_anim_frame(); - self.animating = true; - self.hidden = false; - } + Event::Timer(token) if self.timer.is_expired(event) && !animation_disabled() => { + self.stopwatch.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.stopwatch.start(); self.animating = true; ctx.request_anim_frame(); ctx.request_paint(); } else { - self.token = ctx.request_timer(Self::HIDE_AFTER); + self.timer.start(ctx, Self::HIDE_AFTER); } } else if !self.hidden { self.change_dir(); @@ -399,7 +397,7 @@ impl HideLabelAnimation { HideLabelAnimationState { animating: self.animating, hidden: self.hidden, - elapsed: self.timer.elapsed().to_millis(), + elapsed: self.stopwatch.elapsed().to_millis(), } } } @@ -420,7 +418,7 @@ pub struct Homescreen { bg_image: ImageBuffer>, hold_to_lock: bool, loader: Loader, - delay: Option, + delay: Timer, attach_animation: AttachAnimation, label_anim: HideLabelAnimation, } @@ -458,7 +456,7 @@ impl Homescreen { bg_image: buf, hold_to_lock, loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3), - delay: None, + delay: Timer::new(), attach_animation: AttachAnimation::default(), label_anim: HideLabelAnimation::new(label_width), } @@ -513,11 +511,11 @@ impl Homescreen { if self.loader.is_animating() { self.loader.start_growing(ctx, Instant::now()); } else { - self.delay = Some(ctx.request_timer(LOADER_DELAY)); + self.delay.start(ctx, LOADER_DELAY); } } Event::Touch(TouchEvent::TouchEnd(_)) => { - self.delay = None; + self.delay.stop(); let now = Instant::now(); if self.loader.is_completely_grown(now) { return true; @@ -526,8 +524,7 @@ impl Homescreen { self.loader.start_shrinking(ctx, now); } } - Event::Timer(token) if Some(token) == self.delay => { - self.delay = None; + Event::Timer(token) if self.delay.is_expired(event) => { self.loader.start_growing(ctx, Instant::now()); } _ => {} diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/common.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/common.rs index 58544b75a..12ec60762 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/common.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/common.rs @@ -1,11 +1,10 @@ use crate::{ time::Duration, ui::{ - component::{text::common::TextEdit, Event, EventCtx, TimerToken}, + component::{text::common::TextEdit, Event, EventCtx, Timer}, display::{self, Color, Font}, geometry::{Alignment2D, Offset, Point, Rect}, - shape, - shape::Renderer, + shape::{self, Renderer}, }, }; @@ -17,6 +16,8 @@ pub struct MultiTapKeyboard { timeout: Duration, /// The currently pending state. pending: Option, + /// Timer for clearing the pending state. + timer: Timer, } struct Pending { @@ -25,8 +26,6 @@ struct Pending { /// Index of the key press (how many times the `key` was pressed, minus /// one). press: usize, - /// Timer for clearing the pending state. - timer: TimerToken, } impl MultiTapKeyboard { @@ -35,6 +34,7 @@ impl MultiTapKeyboard { Self { timeout: Duration::from_secs(1), pending: None, + timer: Timer::new(), } } @@ -48,21 +48,17 @@ impl MultiTapKeyboard { self.pending.as_ref().map(|p| p.press) } - /// Return the token for the currently pending timer. - pub fn pending_timer(&self) -> Option { - self.pending.as_ref().map(|p| p.timer) - } - /// Returns `true` if `event` is an `Event::Timer` for the currently pending /// timer. pub fn is_timeout_event(&self, event: Event) -> bool { - matches!((event, self.pending_timer()), (Event::Timer(t), Some(pt)) if pt == t) + self.timer.is_expired(event) } /// Reset to the empty state. Takes `EventCtx` to request a paint pass (to /// either hide or show any pending marker our caller might want to draw /// later). pub fn clear_pending_state(&mut self, ctx: &mut EventCtx) { + self.timer.stop(); if self.pending.is_some() { self.pending = None; ctx.request_paint(); @@ -97,11 +93,8 @@ impl MultiTapKeyboard { // transition only happens as a result of an append op, so the painting should // be requested by handling the `TextEdit`. self.pending = if key_text.len() > 1 { - Some(Pending { - key, - press, - timer: ctx.request_timer(self.timeout), - }) + self.timer.start(ctx, self.timeout); + Some(Pending { key, press }) } else { None }; diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs index 868508d46..25a2fe303 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs @@ -8,7 +8,7 @@ use crate::{ component::{ base::{AttachType, ComponentExt}, text::TextStyle, - Component, Event, EventCtx, Label, Never, Pad, SwipeDirection, TimerToken, + Child, Component, Event, EventCtx, Label, Never, Pad, SwipeDirection, Timer, }, display::Font, event::TouchEvent, @@ -256,7 +256,7 @@ pub struct PinKeyboard<'a> { cancel_btn: Button, confirm_btn: Button, digit_btns: [(Button, usize); DIGIT_COUNT], - warning_timer: Option, + warning_timer: Timer, attach_animation: AttachAnimation, close_animation: CloseAnimation, close_confirm: bool, @@ -295,7 +295,7 @@ impl<'a> PinKeyboard<'a> { .styled(theme::button_pin_confirm()) .initially_enabled(false), digit_btns: Self::generate_digit_buttons(), - warning_timer: None, + warning_timer: Timer::new(), attach_animation: AttachAnimation::default(), close_animation: CloseAnimation::default(), close_confirm: false, @@ -417,10 +417,10 @@ impl Component for PinKeyboard<'_> { match event { // Set up timer to switch off warning prompt. Event::Attach(_) if self.major_warning.is_some() => { - self.warning_timer = Some(ctx.request_timer(Duration::from_secs(2))); + self.warning_timer.start(ctx, Duration::from_secs(2)); } // Hide warning, show major prompt. - Event::Timer(token) if Some(token) == self.warning_timer => { + Event::Timer(_) if self.warning_timer.is_expired(event) => { self.major_warning = None; self.minor_prompt.request_complete_repaint(ctx); ctx.request_paint(); diff --git a/core/embed/rust/src/ui/model_mercury/component/prompt_screen.rs b/core/embed/rust/src/ui/model_mercury/component/prompt_screen.rs index 4937c1f89..4170adc8e 100644 --- a/core/embed/rust/src/ui/model_mercury/component/prompt_screen.rs +++ b/core/embed/rust/src/ui/model_mercury/component/prompt_screen.rs @@ -9,7 +9,6 @@ use super::{HoldToConfirm, TapToConfirm}; /// Component requesting an action from a user. Most typically embedded as a /// content of a Frame and promptin "Tap to confirm" or "Hold to XYZ". -#[derive(Clone)] pub enum PromptScreen { Tap(TapToConfirm), Hold(HoldToConfirm), diff --git a/core/embed/rust/src/ui/model_mercury/component/status_screen.rs b/core/embed/rust/src/ui/model_mercury/component/status_screen.rs index 59aad748a..1d1f975e0 100644 --- a/core/embed/rust/src/ui/model_mercury/component/status_screen.rs +++ b/core/embed/rust/src/ui/model_mercury/component/status_screen.rs @@ -129,7 +129,6 @@ impl StatusAnimation { /// Component showing status of an operation. Most typically embedded as a /// content of a Frame and showing success (checkmark with a circle around). -#[derive(Clone)] pub struct StatusScreen { area: Rect, icon: Icon, @@ -140,7 +139,6 @@ pub struct StatusScreen { msg: Label<'static>, } -#[derive(Clone)] enum DismissType { SwipeUp, Timeout(Timeout), diff --git a/core/embed/rust/src/ui/model_mercury/component/tap_to_confirm.rs b/core/embed/rust/src/ui/model_mercury/component/tap_to_confirm.rs index abad6b797..184e5fdf7 100644 --- a/core/embed/rust/src/ui/model_mercury/component/tap_to_confirm.rs +++ b/core/embed/rust/src/ui/model_mercury/component/tap_to_confirm.rs @@ -113,7 +113,6 @@ impl TapToConfirmAnim { /// Component requesting a Tap to confirm action from a user. Most typically /// embedded as a content of a Frame. -#[derive(Clone)] pub struct TapToConfirm { area: Rect, button: Button, diff --git a/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs b/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs index 5480e8f4b..95ee878ec 100644 --- a/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs +++ b/core/embed/rust/src/ui/model_mercury/component/vertical_menu.rs @@ -172,7 +172,6 @@ impl AttachAnimation { } } -#[derive(Clone)] pub struct VerticalMenu { area: Rect, /// buttons placed vertically from top to bottom diff --git a/core/embed/rust/src/ui/model_tr/component/button_controller.rs b/core/embed/rust/src/ui/model_tr/component/button_controller.rs index 280938879..78c3e9c0f 100644 --- a/core/embed/rust/src/ui/model_tr/component/button_controller.rs +++ b/core/embed/rust/src/ui/model_tr/component/button_controller.rs @@ -4,7 +4,7 @@ use super::{ use crate::{ time::{Duration, Instant}, ui::{ - component::{base::Event, Component, EventCtx, Pad, TimerToken}, + component::{base::Event, Component, EventCtx, Pad, Timer}, event::{ButtonEvent, PhysicalButton}, geometry::Rect, shape::Renderer, @@ -122,7 +122,7 @@ pub struct ButtonContainer { /// `ButtonControllerMsg::Triggered` long_press_ms: u32, /// Timer for sending `ButtonControllerMsg::LongPressed` - long_pressed_timer: Option, + long_pressed_timer: Timer, /// Whether it should even send `ButtonControllerMsg::LongPressed` events /// (optional) send_long_press: bool, @@ -141,7 +141,7 @@ impl ButtonContainer { button_type: ButtonType::from_button_details(pos, btn_details), pressed_since: None, long_press_ms: DEFAULT_LONG_PRESS_MS, - long_pressed_timer: None, + long_pressed_timer: Timer::new(), send_long_press, } } @@ -190,7 +190,7 @@ impl ButtonContainer { Instant::now().saturating_duration_since(since).to_millis() > self.long_press_ms }); self.pressed_since = None; - self.long_pressed_timer = None; + self.long_pressed_timer.stop(); Some(ButtonControllerMsg::Triggered(self.pos, long_press)) } ButtonType::HoldToConfirm(_) => { @@ -216,20 +216,20 @@ impl ButtonContainer { pub fn got_pressed(&mut self, ctx: &mut EventCtx) { self.pressed_since = Some(Instant::now()); if self.send_long_press { - self.long_pressed_timer = - Some(ctx.request_timer(Duration::from_millis(self.long_press_ms))); + self.long_pressed_timer + .start(ctx, Duration::from_millis(self.long_press_ms)); } } /// Reset the pressed information. pub fn reset(&mut self) { self.pressed_since = None; - self.long_pressed_timer = None; + self.long_pressed_timer.stop(); } /// Whether token matches what we have - pub fn is_timer_token(&self, token: TimerToken) -> bool { - self.long_pressed_timer == Some(token) + pub fn is_timer(&self, event: Event) -> bool { + self.long_pressed_timer.is_expired(event) } /// Registering hold event. @@ -380,14 +380,14 @@ impl ButtonController { } } - fn handle_long_press_timer_token(&mut self, token: TimerToken) -> Option { - if self.left_btn.is_timer_token(token) { + fn handle_long_press_timers(&mut self, event: Event) -> Option { + if self.left_btn.is_timer(event) { return Some(ButtonPos::Left); } - if self.middle_btn.is_timer_token(token) { + if self.middle_btn.is_timer(event) { return Some(ButtonPos::Middle); } - if self.right_btn.is_timer_token(token) { + if self.right_btn.is_timer(event) { return Some(ButtonPos::Right); } None @@ -572,11 +572,11 @@ impl Component for ButtonController { event } // Timer - handle clickable properties and HoldToConfirm expiration - Event::Timer(token) => { + Event::Timer(_) => { if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay { - ignore_btn_delay.handle_timer_token(token); + ignore_btn_delay.handle_timers(event); } - if let Some(pos) = self.handle_long_press_timer_token(token) { + if let Some(pos) = self.handle_long_press_timers(event) { return Some(ButtonControllerMsg::LongPressed(pos)); } self.handle_htc_expiration(ctx, event) @@ -624,9 +624,9 @@ struct IgnoreButtonDelay { /// Whether right button is currently clickable right_clickable: bool, /// Timer for setting the left_clickable - left_clickable_timer: Option, + left_clickable_timer: Timer, /// Timer for setting the right_clickable - right_clickable_timer: Option, + right_clickable_timer: Timer, } impl IgnoreButtonDelay { @@ -635,8 +635,8 @@ impl IgnoreButtonDelay { delay: Duration::from_millis(delay_ms), left_clickable: true, right_clickable: true, - left_clickable_timer: None, - right_clickable_timer: None, + left_clickable_timer: Timer::new(), + right_clickable_timer: Timer::new(), } } @@ -644,11 +644,11 @@ impl IgnoreButtonDelay { match pos { ButtonPos::Left => { self.left_clickable = true; - self.left_clickable_timer = None; + self.left_clickable_timer.stop(); } ButtonPos::Right => { self.right_clickable = true; - self.right_clickable_timer = None; + self.right_clickable_timer.stop(); } ButtonPos::Middle => {} } @@ -656,10 +656,10 @@ impl IgnoreButtonDelay { pub fn handle_button_press(&mut self, ctx: &mut EventCtx, button: PhysicalButton) { if matches!(button, PhysicalButton::Left) { - self.right_clickable_timer = Some(ctx.request_timer(self.delay)); + self.right_clickable_timer.start(ctx, self.delay); } if matches!(button, PhysicalButton::Right) { - self.left_clickable_timer = Some(ctx.request_timer(self.delay)); + self.left_clickable_timer.start(ctx, self.delay); } } @@ -673,22 +673,20 @@ impl IgnoreButtonDelay { false } - pub fn handle_timer_token(&mut self, token: TimerToken) { - if self.left_clickable_timer == Some(token) { + pub fn handle_timers(&mut self, event: Event) { + if self.left_clickable_timer.is_expired(event) { self.left_clickable = false; - self.left_clickable_timer = None; } - if self.right_clickable_timer == Some(token) { + if self.right_clickable_timer.is_expired(event) { self.right_clickable = false; - self.right_clickable_timer = None; } } pub fn reset(&mut self) { self.left_clickable = true; self.right_clickable = true; - self.left_clickable_timer = None; - self.right_clickable_timer = None; + self.left_clickable_timer.stop(); + self.right_clickable_timer.stop(); } } @@ -700,7 +698,7 @@ impl IgnoreButtonDelay { /// Can be started e.g. by holding left/right button. pub struct AutomaticMover { /// For requesting timer events repeatedly - timer_token: Option, + timer: Timer, /// Which direction should we go (which button is down) moving_direction: Option, /// How many screens were moved automatically @@ -721,7 +719,7 @@ impl AutomaticMover { } Self { - timer_token: None, + timer: Timer::new(), moving_direction: None, auto_moved_screens: 0, duration_func: default_duration_func, @@ -760,12 +758,12 @@ impl AutomaticMover { pub fn start_moving(&mut self, ctx: &mut EventCtx, button: ButtonPos) { self.auto_moved_screens = 0; self.moving_direction = Some(button); - self.timer_token = Some(ctx.request_timer(self.get_auto_move_duration())); + self.timer.start(ctx, self.get_auto_move_duration()); } pub fn stop_moving(&mut self) { self.moving_direction = None; - self.timer_token = None; + self.timer.stop(); } } @@ -781,15 +779,11 @@ impl Component for AutomaticMover { fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {} fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - // Moving automatically only when we receive a TimerToken that we have - // requested before - if let Event::Timer(token) = event { - if self.timer_token == Some(token) && self.moving_direction.is_some() { - // Request new token and send the appropriate button trigger event - self.timer_token = Some(ctx.request_timer(self.get_auto_move_duration())); - self.auto_moved_screens += 1; - return self.moving_direction; - } + if self.timer.is_expired(event) && self.moving_direction.is_some() { + // Restart timer and send the appropriate button trigger event + self.timer.start(ctx, self.get_auto_move_duration()); + self.auto_moved_screens += 1; + return self.moving_direction; } None } diff --git a/core/embed/rust/src/ui/model_tt/component/button.rs b/core/embed/rust/src/ui/model_tt/component/button.rs index 6bc0ac3e8..16f73c647 100644 --- a/core/embed/rust/src/ui/model_tt/component/button.rs +++ b/core/embed/rust/src/ui/model_tt/component/button.rs @@ -5,7 +5,7 @@ use crate::{ time::Duration, ui::{ component::{ - Component, ComponentExt, Event, EventCtx, FixedHeightBar, MsgMap, Split, TimerToken, + Component, ComponentExt, Event, EventCtx, FixedHeightBar, MsgMap, Split, Timer, }, display::{self, toif::Icon, Color, Font}, event::TouchEvent, @@ -31,7 +31,7 @@ pub struct Button { styles: ButtonStyleSheet, state: State, long_press: Option, - long_timer: Option, + long_timer: Timer, haptics: bool, } @@ -48,7 +48,7 @@ impl Button { styles: theme::button_default(), state: State::Initial, long_press: None, - long_timer: None, + long_timer: Timer::new(), haptics: true, } } @@ -317,7 +317,7 @@ impl Component for Button { } self.set(ctx, State::Pressed); if let Some(duration) = self.long_press { - self.long_timer = Some(ctx.request_timer(duration)); + self.long_timer.start(ctx, duration) } return Some(ButtonMsg::Pressed); } @@ -349,21 +349,18 @@ impl Component for Button { _ => { // Touch finished outside our area. self.set(ctx, State::Initial); - self.long_timer = None; + self.long_timer.stop(); } } } - Event::Timer(token) => { - if self.long_timer == Some(token) { - self.long_timer = None; - if matches!(self.state, State::Pressed) { - #[cfg(feature = "haptic")] - if self.haptics { - haptic::play(HapticEffect::ButtonPress); - } - self.set(ctx, State::Initial); - return Some(ButtonMsg::LongPressed); + Event::Timer(_) if self.long_timer.is_expired(event) => { + if matches!(self.state, State::Pressed) { + #[cfg(feature = "haptic")] + if self.haptics { + haptic::play(HapticEffect::ButtonPress); } + self.set(ctx, State::Initial); + return Some(ButtonMsg::LongPressed); } } _ => {} diff --git a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs index e8c70f7b0..a5be1dbf0 100644 --- a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs @@ -7,7 +7,7 @@ use crate::{ translations::TR, trezorhal::usb::usb_configured, ui::{ - component::{Component, Event, EventCtx, Pad, TimerToken}, + component::{Component, Event, EventCtx, Pad, Timer}, display::{ self, image::{ImageInfo, ToifFormat}, @@ -58,7 +58,7 @@ pub struct Homescreen { loader: Loader, pad: Pad, paint_notification_only: bool, - delay: Option, + delay: Timer, } pub enum HomescreenMsg { @@ -79,7 +79,7 @@ impl Homescreen { loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3), pad: Pad::with_background(theme::BG), paint_notification_only: false, - delay: None, + delay: Timer::new(), } } @@ -152,11 +152,11 @@ impl Homescreen { if self.loader.is_animating() { self.loader.start_growing(ctx, Instant::now()); } else { - self.delay = Some(ctx.request_timer(LOADER_DELAY)); + self.delay.start(ctx, LOADER_DELAY); } } Event::Touch(TouchEvent::TouchEnd(_)) => { - self.delay = None; + self.delay.stop(); let now = Instant::now(); if self.loader.is_completely_grown(now) { return true; @@ -165,8 +165,7 @@ impl Homescreen { self.loader.start_shrinking(ctx, now); } } - Event::Timer(token) if Some(token) == self.delay => { - self.delay = None; + Event::Timer(_) if self.delay.is_expired(event) => { self.pad.clear(); self.paint_notification_only = false; self.loader.start_growing(ctx, Instant::now()); diff --git a/core/embed/rust/src/ui/model_tt/component/keyboard/common.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/common.rs index d4dcb7b78..9e3db780a 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/common.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/common.rs @@ -1,7 +1,7 @@ use crate::{ time::Duration, ui::{ - component::{text::common::TextEdit, Event, EventCtx, TimerToken}, + component::{text::common::TextEdit, Event, EventCtx, Timer}, display::{self, Color, Font}, geometry::{Offset, Point, Rect}, shape, @@ -24,7 +24,16 @@ struct Pending { /// one). press: usize, /// Timer for clearing the pending state. - timer: TimerToken, + timer: Timer, +} + +impl Pending { + /// Create a new pending state for a key. + fn start(ctx: &mut EventCtx, key: usize, press: usize, timeout: Duration) -> Self { + let mut timer = Timer::new(); + timer.start(ctx, timeout); + Self { key, press, timer } + } } impl MultiTapKeyboard { @@ -47,14 +56,14 @@ impl MultiTapKeyboard { } /// Return the token for the currently pending timer. - pub fn pending_timer(&self) -> Option { - self.pending.as_ref().map(|p| p.timer) + pub fn pending_timer(&self) -> Option<&Timer> { + self.pending.as_ref().map(|p| &p.timer) } /// Returns `true` if `event` is an `Event::Timer` for the currently pending /// timer. pub fn is_timeout_event(&self, event: Event) -> bool { - matches!((event, self.pending_timer()), (Event::Timer(t), Some(pt)) if pt == t) + self.pending_timer().map_or(false, |t| t.is_expired(event)) } /// Reset to the empty state. Takes `EventCtx` to request a paint pass (to @@ -95,11 +104,7 @@ impl MultiTapKeyboard { // transition only happens as a result of an append op, so the painting should // be requested by handling the `TextEdit`. self.pending = if key_text.len() > 1 { - Some(Pending { - key, - press, - timer: ctx.request_timer(self.timeout), - }) + Some(Pending::start(ctx, key, press, self.timeout)) } else { None }; diff --git a/core/embed/rust/src/ui/model_tt/component/keyboard/pin.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/pin.rs index 3417b60ea..3abe6b0a3 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/pin.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/pin.rs @@ -7,7 +7,7 @@ use crate::{ ui::{ component::{ base::ComponentExt, text::TextStyle, Child, Component, Event, EventCtx, Label, Maybe, - Never, Pad, TimerToken, + Never, Pad, Timer, }, display::{self, Font}, event::TouchEvent, @@ -54,7 +54,7 @@ pub struct PinKeyboard<'a> { cancel_btn: Child>, confirm_btn: Child