mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-15 12:08:59 +00:00
refactor(core/ui): reusable timers
This commit is contained in:
parent
d385667b4a
commit
9e911605da
@ -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);
|
||||
}
|
||||
|
@ -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<TimerToken>,
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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"))]
|
||||
|
@ -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<TimerToken>,
|
||||
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<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.expire(event).then_some(())
|
||||
}
|
||||
|
||||
fn paint(&mut self) {}
|
||||
|
@ -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,
|
||||
|
@ -21,7 +21,6 @@ use super::{theme, Frame, FrameMsg};
|
||||
|
||||
const MAX_XPUBS: usize = 16;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AddressDetails {
|
||||
details: Frame<Paragraphs<ParagraphVecShort<'static>>>,
|
||||
xpub_view: Frame<Paragraphs<Paragraph<'static>>>,
|
||||
|
@ -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<Insets>,
|
||||
@ -33,7 +31,7 @@ pub struct Button {
|
||||
radius: Option<u8>,
|
||||
state: State,
|
||||
long_press: Option<Duration>,
|
||||
long_timer: Option<TimerToken>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -79,7 +79,6 @@ impl HorizontalSwipe {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Frame<T> {
|
||||
bounds: Rect,
|
||||
content: T,
|
||||
|
@ -59,7 +59,7 @@ impl AttachAnimation {
|
||||
}
|
||||
|
||||
const BUTTON_EXPAND_BORDER: i16 = 32;
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct Header {
|
||||
area: Rect,
|
||||
title: Label<'static>,
|
||||
|
@ -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,
|
||||
|
@ -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<Rgb565Canvas<'static>>,
|
||||
hold_to_lock: bool,
|
||||
loader: Loader,
|
||||
delay: Option<TimerToken>,
|
||||
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());
|
||||
}
|
||||
_ => {}
|
||||
|
@ -93,7 +93,7 @@ impl Component for Bip39Input {
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
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)
|
||||
|
@ -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<Pending>,
|
||||
/// 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<TimerToken> {
|
||||
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
|
||||
};
|
||||
|
@ -321,7 +321,7 @@ impl Component for PassphraseKeyboard {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
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;
|
||||
}
|
||||
|
@ -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<TimerToken>,
|
||||
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();
|
||||
|
@ -106,7 +106,7 @@ impl Component for Slip39Input {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
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);
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -167,7 +167,6 @@ impl AttachAnimation {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VerticalMenu {
|
||||
/// buttons placed vertically from top to bottom
|
||||
buttons: VerticalMenuButtons,
|
||||
|
@ -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<TimerToken>,
|
||||
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<ButtonPos> {
|
||||
if self.left_btn.is_timer_token(token) {
|
||||
fn handle_long_press_timers(&mut self, event: Event) -> Option<ButtonPos> {
|
||||
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<TimerToken>,
|
||||
left_clickable_timer: Timer,
|
||||
/// Timer for setting the right_clickable
|
||||
right_clickable_timer: Option<TimerToken>,
|
||||
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<TimerToken>,
|
||||
timer: Timer,
|
||||
/// Which direction should we go (which button is down)
|
||||
moving_direction: Option<ButtonPos>,
|
||||
/// 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<Self::Msg> {
|
||||
// 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
|
||||
}
|
||||
|
@ -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<Duration>,
|
||||
long_timer: Option<TimerToken>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -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<TimerToken>,
|
||||
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());
|
||||
|
@ -93,7 +93,7 @@ impl Component for Bip39Input {
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
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)
|
||||
|
@ -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<TimerToken> {
|
||||
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
|
||||
};
|
||||
|
@ -221,9 +221,15 @@ impl Component for PassphraseKeyboard {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
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) {
|
||||
|
@ -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<Maybe<Button>>,
|
||||
confirm_btn: Child<Button>,
|
||||
digit_btns: [Child<Button>; DIGIT_COUNT],
|
||||
warning_timer: Option<TimerToken>,
|
||||
warning_timer: Timer,
|
||||
}
|
||||
|
||||
impl<'a> PinKeyboard<'a> {
|
||||
@ -99,7 +99,7 @@ impl<'a> PinKeyboard<'a> {
|
||||
.initially_enabled(false)
|
||||
.into_child(),
|
||||
digit_btns: Self::generate_digit_buttons(),
|
||||
warning_timer: None,
|
||||
warning_timer: Timer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,10 +201,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.textbox_pad.clear();
|
||||
self.minor_prompt.request_complete_repaint(ctx);
|
||||
|
@ -106,7 +106,7 @@ impl Component for Slip39Input {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
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);
|
||||
|
@ -360,11 +360,13 @@ class Layout(Generic[T]):
|
||||
# do not schedule another animation frame if one is already scheduled
|
||||
return
|
||||
|
||||
assert token not in self.timers
|
||||
task = timer_task()
|
||||
self.timers[token] = task
|
||||
task = self.timers.get(token)
|
||||
if task is None:
|
||||
task = timer_task()
|
||||
self.timers[token] = task
|
||||
|
||||
deadline = utime.ticks_add(utime.ticks_ms(), duration_ms)
|
||||
loop.schedule(task, deadline=deadline)
|
||||
loop.schedule(task, deadline=deadline, reschedule=True)
|
||||
|
||||
def _emit_message(self, msg: Any) -> None:
|
||||
"""Process a message coming out of the Rust layout. Set is as a result and shut
|
||||
|
Loading…
Reference in New Issue
Block a user