diff --git a/core/embed/rust/src/ui/component/base.rs b/core/embed/rust/src/ui/component/base.rs index d63907d41..28bfa7175 100644 --- a/core/embed/rust/src/ui/component/base.rs +++ b/core/embed/rust/src/ui/component/base.rs @@ -402,6 +402,11 @@ pub struct TimerToken(u32); impl TimerToken { /// Value of an invalid (or missing) token. pub const INVALID: TimerToken = TimerToken(0); + /// Reserved value of the animation frame timer. + pub const ANIM_FRAME: TimerToken = TimerToken(1); + + /// Starting token value + const STARTING_TOKEN: u32 = 2; pub const fn from_raw(raw: u32) -> Self { Self(raw) @@ -410,11 +415,77 @@ impl TimerToken { pub const fn into_raw(self) -> u32 { self.0 } + + pub fn next_token() -> Self { + static mut NEXT_TOKEN: TimerToken = Self(TimerToken::STARTING_TOKEN); + + // SAFETY: we are in single-threaded environment + let token = unsafe { NEXT_TOKEN }; + let next = { + if token.0 == u32::MAX { + TimerToken(Self::STARTING_TOKEN) + } else { + TimerToken(token.0 + 1) + } + }; + // SAFETY: we are in single-threaded environment + unsafe { NEXT_TOKEN = next }; + token + } +} + +#[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))] +pub struct Timer { + token: TimerToken, + running: bool, +} + +impl Timer { + /// Create a new timer. + pub const fn new() -> Self { + Self { + token: TimerToken::INVALID, + running: false, + } + } + + /// 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) { + if self.token == TimerToken::INVALID { + self.token = TimerToken::next_token(); + } + self.running = true; + ctx.register_timer(self.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.running = false; + } + + /// 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 expire(&mut self, event: Event) -> bool { + if self.running && event == Event::Timer(self.token) { + self.running = false; + true + } else { + false + } + } } pub struct EventCtx { timers: Vec<(TimerToken, Duration), { Self::MAX_TIMERS }>, - next_token: u32, place_requested: bool, paint_requested: bool, anim_frame_scheduled: bool, @@ -433,17 +504,12 @@ impl EventCtx { /// How long into the future we should schedule the animation frame timer. const ANIM_FRAME_DURATION: Duration = Duration::from_millis(1); - // 0 == `TimerToken::INVALID`, - // 1 == `Self::ANIM_FRAME_TIMER`. - const STARTING_TIMER_TOKEN: u32 = 2; - /// Maximum amount of timers requested in one event tick. const MAX_TIMERS: usize = 4; pub fn new() -> Self { Self { timers: Vec::new(), - next_token: Self::STARTING_TIMER_TOKEN, place_requested: false, paint_requested: false, anim_frame_scheduled: false, @@ -476,13 +542,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 { @@ -491,6 +550,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; } @@ -551,10 +614,7 @@ impl EventCtx { #[cfg(feature = "ui_debug")] assert!(self.button_request.is_none()); // replace self with a new instance, keeping only the fields we care about - *self = Self { - next_token: self.next_token, - ..Self::new() - } + *self = Self::new(); } fn register_timer(&mut self, token: TimerToken, duration: Duration) { @@ -566,18 +626,6 @@ impl EventCtx { } } - fn next_timer_token(&mut self) -> TimerToken { - let token = TimerToken(self.next_token); - // We start again from the beginning if the token counter overflows. This would - // probably happen in case of a bug and a long-running session. Let's risk the - // collisions in such case. - self.next_token = self - .next_token - .checked_add(1) - .unwrap_or(Self::STARTING_TIMER_TOKEN); - token - } - pub fn set_transition_out(&mut self, attach_type: AttachType) { self.transition_out = Some(attach_type); } diff --git a/core/embed/rust/src/ui/component/marquee.rs b/core/embed/rust/src/ui/component/marquee.rs index d6a91386c..fff8ca83d 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.expire(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 32cea86d3..b6c2b8ad4 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, FlowMsg, Never, TimerToken}; +pub use base::{Child, Component, ComponentExt, Event, EventCtx, FlowMsg, 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..9d5c83e64 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.expire(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 9e0a7b76e..1b8af1ef7 100644 --- a/core/embed/rust/src/ui/layout/obj.rs +++ b/core/embed/rust/src/ui/layout/obj.rs @@ -29,7 +29,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 928e81567..7e2978a5a 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 a68cb4f77..2d2d0282e 100644 --- a/core/embed/rust/src/ui/model_mercury/component/button.rs +++ b/core/embed/rust/src/ui/model_mercury/component/button.rs @@ -4,12 +4,11 @@ use crate::{ strutil::TString, time::Duration, ui::{ - component::{Component, Event, EventCtx, TimerToken}, + component::{Component, Event, EventCtx, Timer}, display::{self, toif::Icon, Color, Font}, event::TouchEvent, geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect}, - shape, - shape::Renderer, + shape::{self, Renderer}, util::split_two_lines, }, }; @@ -23,7 +22,6 @@ pub enum ButtonMsg { LongPressed, } -#[derive(Clone)] pub struct Button { area: Rect, touch_expand: Option, @@ -33,7 +31,7 @@ pub struct Button { radius: Option, state: State, long_press: Option, - long_timer: Option, + long_timer: Timer, haptic: bool, } @@ -53,7 +51,7 @@ impl Button { radius: None, state: State::Initial, long_press: None, - long_timer: None, + long_timer: Timer::new(), haptic: true, } } @@ -312,7 +310,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); } @@ -344,13 +342,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(); } } } @@ -363,28 +361,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.expire(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 de90949e9..13f4666f6 100644 --- a/core/embed/rust/src/ui/model_mercury/component/frame.rs +++ b/core/embed/rust/src/ui/model_mercury/component/frame.rs @@ -79,7 +79,6 @@ impl HorizontalSwipe { } } -#[derive(Clone)] pub struct Frame { bounds: Rect, content: T, 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 8b22248ed..45a3d48cf 100644 --- a/core/embed/rust/src/ui/model_mercury/component/header.rs +++ b/core/embed/rust/src/ui/model_mercury/component/header.rs @@ -59,7 +59,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..379f2b221 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.expire(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.expire(event) => { self.loader.start_growing(ctx, Instant::now()); } _ => {} diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/bip39.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/bip39.rs index 582092778..9eec4b179 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/bip39.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/bip39.rs @@ -93,7 +93,7 @@ impl Component for Bip39Input { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { self.button_suggestion.event(ctx, event); - if self.multi_tap.is_timeout_event(event) { + if self.multi_tap.timeout_event(event) { self.on_timeout(ctx) } else if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) { self.on_input_click(ctx) 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 628e0705f..0f52d6863 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) + pub fn timeout_event(&mut self, event: Event) -> bool { + self.timer.expire(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/passphrase.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs index afca80497..52651ff06 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs @@ -321,7 +321,7 @@ impl Component for PassphraseKeyboard { } fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - if self.input.multi_tap.is_timeout_event(event) { + if self.input.multi_tap.timeout_event(event) { self.input.multi_tap.clear_pending_state(ctx); return 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 62c3b9137..6561ad802 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, TimerToken, + Component, Event, EventCtx, Label, Never, Pad, 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.expire(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/keyboard/slip39.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/slip39.rs index abd6bcadd..de7ec4f1a 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/slip39.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/slip39.rs @@ -106,7 +106,7 @@ impl Component for Slip39Input { } fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - if self.multi_tap.is_timeout_event(event) { + if self.multi_tap.timeout_event(event) { // Timeout occurred. Reset the pending key. self.multi_tap.clear_pending_state(ctx); return Some(MnemonicInputMsg::TimedOut); 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 214f603c4..9737523de 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 @@ -11,7 +11,6 @@ use super::{super::theme, BinarySelection, ButtonContent, HoldToConfirm, TapToCo /// - Tap to confirm /// - Hold to confirm /// - Yes/No selection -#[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 d4145a30b..d9cff5b86 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 bc82d304a..f8d423997 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 @@ -167,7 +167,6 @@ impl AttachAnimation { } } -#[derive(Clone)] pub struct VerticalMenu { /// buttons placed vertically from top to bottom buttons: VerticalMenuButtons, 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 242712fd6..c50d6d704 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 timer_event(&mut self, event: Event) -> bool { + self.long_pressed_timer.expire(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.timer_event(event) { return Some(ButtonPos::Left); } - if self.middle_btn.is_timer_token(token) { + if self.middle_btn.timer_event(event) { return Some(ButtonPos::Middle); } - if self.right_btn.is_timer_token(token) { + if self.right_btn.timer_event(event) { return Some(ButtonPos::Right); } None @@ -575,11 +575,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) @@ -627,9 +627,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 { @@ -638,8 +638,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(), } } @@ -647,11 +647,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 => {} } @@ -659,10 +659,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); } } @@ -676,22 +676,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.expire(event) { self.left_clickable = false; - self.left_clickable_timer = None; } - if self.right_clickable_timer == Some(token) { + if self.right_clickable_timer.expire(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(); } } @@ -703,7 +701,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 @@ -724,7 +722,7 @@ impl AutomaticMover { } Self { - timer_token: None, + timer: Timer::new(), moving_direction: None, auto_moved_screens: 0, duration_func: default_duration_func, @@ -763,12 +761,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(); } } @@ -784,15 +782,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.expire(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..725756cc3 100644 --- a/core/embed/rust/src/ui/model_tt/component/button.rs +++ b/core/embed/rust/src/ui/model_tt/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::{Alignment2D, Insets, Offset, Point, Rect}, - shape, - shape::Renderer, + shape::{self, Renderer}, }, }; @@ -31,7 +30,7 @@ pub struct Button { styles: ButtonStyleSheet, state: State, long_press: Option, - long_timer: Option, + long_timer: Timer, haptics: bool, } @@ -48,7 +47,7 @@ impl Button { styles: theme::button_default(), state: State::Initial, long_press: None, - long_timer: None, + long_timer: Timer::new(), haptics: true, } } @@ -317,7 +316,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 +348,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.expire(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..600fbb96c 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.expire(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/bip39.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/bip39.rs index c63c3fde5..906406303 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/bip39.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/bip39.rs @@ -93,7 +93,7 @@ impl Component for Bip39Input { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { self.button_suggestion.event(ctx, event); - if self.multi_tap.is_timeout_event(event) { + if self.multi_tap.timeout_event(event) { self.on_timeout(ctx) } else if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) { self.on_input_click(ctx) 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..b22274536 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 { @@ -46,15 +55,10 @@ 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) + pub fn timeout_event(&mut self, event: Event) -> bool { + self.pending.as_mut().map_or(false, |t| t.timer.expire(event)) } /// Reset to the empty state. Takes `EventCtx` to request a paint pass (to @@ -95,11 +99,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/passphrase.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/passphrase.rs index 1b10fa621..93e003344 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/passphrase.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/passphrase.rs @@ -221,9 +221,15 @@ impl Component for PassphraseKeyboard { } fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - if self.input.inner().multi_tap.is_timeout_event(event) { - self.input - .mutate(ctx, |ctx, i| i.multi_tap.clear_pending_state(ctx)); + let multitap_timeout = self.input.mutate(ctx, |ctx, i| { + if i.multi_tap.timeout_event(event) { + i.multi_tap.clear_pending_state(ctx); + true + } else { + false + } + }); + if multitap_timeout { return None; } if let Some(swipe) = self.page_swipe.event(ctx, event) { 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..e373c2f58 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