Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restartable timers #3702

Open
wants to merge 5 commits into
base: matejcik/global-layout-only3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions core/.changelog.d/3633.changed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved device responsiveness by removing unnecessary screen refreshes.
47 changes: 40 additions & 7 deletions core/embed/rust/src/ui/component/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,42 @@ impl TimerToken {
}
}

#[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))]
pub struct Timer(Option<TimerToken>);

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,
Expand Down Expand Up @@ -508,13 +544,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 {
Expand All @@ -523,6 +552,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;
}
Expand Down
75 changes: 36 additions & 39 deletions core/embed/rust/src/ui/component/marquee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
display::{Color, Font},
geometry::Rect,
Expand All @@ -24,7 +24,7 @@ enum State {

pub struct Marquee {
area: Rect,
pause_token: Option<TimerToken>,
pause_timer: Timer,
min_offset: i16,
max_offset: i16,
state: State,
Expand All @@ -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,
Expand Down Expand Up @@ -141,53 +141,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
}

Expand Down
2 changes: 1 addition & 1 deletion core/embed/rust/src/ui/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub mod qr_code;
pub mod text;
pub mod timeout;

pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Root, TimerToken};
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Root, Timer};
pub use border::Border;
pub use empty::Empty;
pub use label::Label;
Expand Down
21 changes: 6 additions & 15 deletions core/embed/rust/src/ui/component/timeout.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use crate::{
time::Duration,
ui::{
component::{Component, Event, EventCtx, TimerToken},
component::{Component, Event, EventCtx, Timer},
geometry::Rect,
},
};

pub struct Timeout {
time_ms: u32,
timer: Option<TimerToken>,
timer: Timer,
}

impl Timeout {
pub fn new(time_ms: u32) -> Self {
Self {
time_ms,
timer: None,
timer: Timer::new(),
}
}
}
Expand All @@ -28,19 +28,10 @@ impl Component for Timeout {
}

fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
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) {}
Expand Down
2 changes: 1 addition & 1 deletion core/embed/rust/src/ui/layout/obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::{
},
time::Duration,
ui::{
component::{Component, Event, EventCtx, Never, Root, TimerToken},
component::{base::TimerToken, Component, Event, EventCtx, Never, Root},
constant,
display::sync,
geometry::Rect,
Expand Down