mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-23 13:51:00 +00:00
refactor(core/ui): reusable timers
This commit is contained in:
parent
3de77db2b8
commit
adaf0b7871
@ -402,6 +402,11 @@ pub struct TimerToken(u32);
|
|||||||
impl TimerToken {
|
impl TimerToken {
|
||||||
/// Value of an invalid (or missing) token.
|
/// Value of an invalid (or missing) token.
|
||||||
pub const INVALID: TimerToken = TimerToken(0);
|
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 {
|
pub const fn from_raw(raw: u32) -> Self {
|
||||||
Self(raw)
|
Self(raw)
|
||||||
@ -410,11 +415,77 @@ impl TimerToken {
|
|||||||
pub const fn into_raw(self) -> u32 {
|
pub const fn into_raw(self) -> u32 {
|
||||||
self.0
|
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 {
|
pub struct EventCtx {
|
||||||
timers: Vec<(TimerToken, Duration), { Self::MAX_TIMERS }>,
|
timers: Vec<(TimerToken, Duration), { Self::MAX_TIMERS }>,
|
||||||
next_token: u32,
|
|
||||||
place_requested: bool,
|
place_requested: bool,
|
||||||
paint_requested: bool,
|
paint_requested: bool,
|
||||||
anim_frame_scheduled: bool,
|
anim_frame_scheduled: bool,
|
||||||
@ -433,17 +504,12 @@ impl EventCtx {
|
|||||||
/// How long into the future we should schedule the animation frame timer.
|
/// How long into the future we should schedule the animation frame timer.
|
||||||
const ANIM_FRAME_DURATION: Duration = Duration::from_millis(1);
|
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.
|
/// Maximum amount of timers requested in one event tick.
|
||||||
const MAX_TIMERS: usize = 4;
|
const MAX_TIMERS: usize = 4;
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
timers: Vec::new(),
|
timers: Vec::new(),
|
||||||
next_token: Self::STARTING_TIMER_TOKEN,
|
|
||||||
place_requested: false,
|
place_requested: false,
|
||||||
paint_requested: false,
|
paint_requested: false,
|
||||||
anim_frame_scheduled: false,
|
anim_frame_scheduled: false,
|
||||||
@ -476,13 +542,6 @@ impl EventCtx {
|
|||||||
self.paint_requested = true;
|
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.
|
/// Request an animation frame timer to fire as soon as possible.
|
||||||
pub fn request_anim_frame(&mut self) {
|
pub fn request_anim_frame(&mut self) {
|
||||||
if !self.anim_frame_scheduled {
|
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) {
|
pub fn request_repaint_root(&mut self) {
|
||||||
self.root_repaint_requested = true;
|
self.root_repaint_requested = true;
|
||||||
}
|
}
|
||||||
@ -551,10 +614,7 @@ impl EventCtx {
|
|||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
assert!(self.button_request.is_none());
|
assert!(self.button_request.is_none());
|
||||||
// replace self with a new instance, keeping only the fields we care about
|
// replace self with a new instance, keeping only the fields we care about
|
||||||
*self = Self {
|
*self = Self::new();
|
||||||
next_token: self.next_token,
|
|
||||||
..Self::new()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_timer(&mut self, token: TimerToken, duration: Duration) {
|
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) {
|
pub fn set_transition_out(&mut self, attach_type: AttachType) {
|
||||||
self.transition_out = Some(attach_type);
|
self.transition_out = Some(attach_type);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use crate::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
ui::{
|
ui::{
|
||||||
animation::Animation,
|
animation::Animation,
|
||||||
component::{Component, Event, EventCtx, Never, TimerToken},
|
component::{Component, Event, EventCtx, Never, Timer},
|
||||||
display::{self, Color, Font},
|
display::{self, Color, Font},
|
||||||
geometry::{Offset, Rect},
|
geometry::{Offset, Rect},
|
||||||
shape::{self, Renderer},
|
shape::{self, Renderer},
|
||||||
@ -24,7 +24,7 @@ enum State {
|
|||||||
|
|
||||||
pub struct Marquee {
|
pub struct Marquee {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
pause_token: Option<TimerToken>,
|
pause_timer: Timer,
|
||||||
min_offset: i16,
|
min_offset: i16,
|
||||||
max_offset: i16,
|
max_offset: i16,
|
||||||
state: State,
|
state: State,
|
||||||
@ -40,7 +40,7 @@ impl Marquee {
|
|||||||
pub fn new(text: TString<'static>, font: Font, fg: Color, bg: Color) -> Self {
|
pub fn new(text: TString<'static>, font: Font, fg: Color, bg: Color) -> Self {
|
||||||
Self {
|
Self {
|
||||||
area: Rect::zero(),
|
area: Rect::zero(),
|
||||||
pause_token: None,
|
pause_timer: Timer::new(),
|
||||||
min_offset: 0,
|
min_offset: 0,
|
||||||
max_offset: 0,
|
max_offset: 0,
|
||||||
state: State::Initial,
|
state: State::Initial,
|
||||||
@ -154,17 +154,14 @@ impl Component for Marquee {
|
|||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
if let Event::Timer(token) = event {
|
if self.pause_timer.expire(event) {
|
||||||
if self.pause_token == Some(token) {
|
|
||||||
match self.state {
|
match self.state {
|
||||||
State::PauseLeft => {
|
State::PauseLeft => {
|
||||||
let anim =
|
let anim = Animation::new(self.max_offset, self.min_offset, self.duration, now);
|
||||||
Animation::new(self.max_offset, self.min_offset, self.duration, now);
|
|
||||||
self.state = State::Right(anim);
|
self.state = State::Right(anim);
|
||||||
}
|
}
|
||||||
State::PauseRight => {
|
State::PauseRight => {
|
||||||
let anim =
|
let anim = Animation::new(self.min_offset, self.max_offset, self.duration, now);
|
||||||
Animation::new(self.min_offset, self.max_offset, self.duration, now);
|
|
||||||
self.state = State::Left(anim);
|
self.state = State::Left(anim);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -175,7 +172,7 @@ impl Component for Marquee {
|
|||||||
ctx.request_anim_frame();
|
ctx.request_anim_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
if token == EventCtx::ANIM_FRAME_TIMER {
|
if EventCtx::is_anim_frame(event) {
|
||||||
if self.is_animating() {
|
if self.is_animating() {
|
||||||
// We have something to paint, so request to be painted in the next pass.
|
// We have something to paint, so request to be painted in the next pass.
|
||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
@ -187,20 +184,20 @@ impl Component for Marquee {
|
|||||||
match self.state {
|
match self.state {
|
||||||
State::Right(_) => {
|
State::Right(_) => {
|
||||||
if self.is_at_right(now) {
|
if self.is_at_right(now) {
|
||||||
self.pause_token = Some(ctx.request_timer(self.pause));
|
self.pause_timer.start(ctx, self.pause);
|
||||||
self.state = State::PauseRight;
|
self.state = State::PauseRight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State::Left(_) => {
|
State::Left(_) => {
|
||||||
if self.is_at_left(now) {
|
if self.is_at_left(now) {
|
||||||
self.pause_token = Some(ctx.request_timer(self.pause));
|
self.pause_timer.start(ctx, self.pause);
|
||||||
self.state = State::PauseLeft;
|
self.state = State::PauseLeft;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ pub mod text;
|
|||||||
pub mod timeout;
|
pub mod timeout;
|
||||||
|
|
||||||
pub use bar::Bar;
|
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 border::Border;
|
||||||
pub use button_request::{ButtonRequestExt, SendButtonRequest};
|
pub use button_request::{ButtonRequestExt, SendButtonRequest};
|
||||||
#[cfg(all(feature = "jpeg", feature = "ui_image_buffer", feature = "micropython"))]
|
#[cfg(all(feature = "jpeg", feature = "ui_image_buffer", feature = "micropython"))]
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx, TimerToken},
|
component::{Component, Event, EventCtx, Timer},
|
||||||
geometry::Rect,
|
geometry::Rect,
|
||||||
shape::Renderer,
|
shape::Renderer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Timeout {
|
pub struct Timeout {
|
||||||
time_ms: u32,
|
time_ms: u32,
|
||||||
timer: Option<TimerToken>,
|
timer: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timeout {
|
impl Timeout {
|
||||||
pub fn new(time_ms: u32) -> Self {
|
pub fn new(time_ms: u32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
time_ms,
|
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> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
match event {
|
if matches!(event, Event::Attach(_)) {
|
||||||
// Set up timer.
|
self.timer.start(ctx, Duration::from_millis(self.time_ms));
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
self.timer.expire(event).then_some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self) {}
|
fn paint(&mut self) {}
|
||||||
|
@ -29,7 +29,7 @@ use crate::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
ui::{
|
ui::{
|
||||||
button_request::ButtonRequest,
|
button_request::ButtonRequest,
|
||||||
component::{base::AttachType, Component, Event, EventCtx, Never, TimerToken},
|
component::{base::{AttachType, TimerToken}, Component, Event, EventCtx, Never},
|
||||||
constant, display,
|
constant, display,
|
||||||
event::USBEvent,
|
event::USBEvent,
|
||||||
geometry::Rect,
|
geometry::Rect,
|
||||||
|
@ -21,7 +21,6 @@ use super::{theme, Frame, FrameMsg};
|
|||||||
|
|
||||||
const MAX_XPUBS: usize = 16;
|
const MAX_XPUBS: usize = 16;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AddressDetails {
|
pub struct AddressDetails {
|
||||||
details: Frame<Paragraphs<ParagraphVecShort<'static>>>,
|
details: Frame<Paragraphs<ParagraphVecShort<'static>>>,
|
||||||
xpub_view: Frame<Paragraphs<Paragraph<'static>>>,
|
xpub_view: Frame<Paragraphs<Paragraph<'static>>>,
|
||||||
|
@ -4,12 +4,11 @@ use crate::{
|
|||||||
strutil::TString,
|
strutil::TString,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx, TimerToken},
|
component::{Component, Event, EventCtx, Timer},
|
||||||
display::{self, toif::Icon, Color, Font},
|
display::{self, toif::Icon, Color, Font},
|
||||||
event::TouchEvent,
|
event::TouchEvent,
|
||||||
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
|
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
|
||||||
shape,
|
shape::{self, Renderer},
|
||||||
shape::Renderer,
|
|
||||||
util::split_two_lines,
|
util::split_two_lines,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -23,7 +22,6 @@ pub enum ButtonMsg {
|
|||||||
LongPressed,
|
LongPressed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
touch_expand: Option<Insets>,
|
touch_expand: Option<Insets>,
|
||||||
@ -33,7 +31,7 @@ pub struct Button {
|
|||||||
radius: Option<u8>,
|
radius: Option<u8>,
|
||||||
state: State,
|
state: State,
|
||||||
long_press: Option<Duration>,
|
long_press: Option<Duration>,
|
||||||
long_timer: Option<TimerToken>,
|
long_timer: Timer,
|
||||||
haptic: bool,
|
haptic: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +51,7 @@ impl Button {
|
|||||||
radius: None,
|
radius: None,
|
||||||
state: State::Initial,
|
state: State::Initial,
|
||||||
long_press: None,
|
long_press: None,
|
||||||
long_timer: None,
|
long_timer: Timer::new(),
|
||||||
haptic: true,
|
haptic: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,7 +310,7 @@ impl Component for Button {
|
|||||||
}
|
}
|
||||||
self.set(ctx, State::Pressed);
|
self.set(ctx, State::Pressed);
|
||||||
if let Some(duration) = self.long_press {
|
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);
|
return Some(ButtonMsg::Pressed);
|
||||||
}
|
}
|
||||||
@ -344,13 +342,13 @@ impl Component for Button {
|
|||||||
State::Pressed => {
|
State::Pressed => {
|
||||||
// Touch finished outside our area.
|
// Touch finished outside our area.
|
||||||
self.set(ctx, State::Initial);
|
self.set(ctx, State::Initial);
|
||||||
self.long_timer = None;
|
self.long_timer.stop();
|
||||||
return Some(ButtonMsg::Released);
|
return Some(ButtonMsg::Released);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Touch finished outside our area.
|
// Touch finished outside our area.
|
||||||
self.set(ctx, State::Initial);
|
self.set(ctx, State::Initial);
|
||||||
self.long_timer = None;
|
self.long_timer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,20 +361,18 @@ impl Component for Button {
|
|||||||
State::Pressed => {
|
State::Pressed => {
|
||||||
// Touch aborted
|
// Touch aborted
|
||||||
self.set(ctx, State::Initial);
|
self.set(ctx, State::Initial);
|
||||||
self.long_timer = None;
|
self.long_timer.stop();
|
||||||
return Some(ButtonMsg::Released);
|
return Some(ButtonMsg::Released);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Irrelevant touch abort
|
// Irrelevant touch abort
|
||||||
self.set(ctx, State::Initial);
|
self.set(ctx, State::Initial);
|
||||||
self.long_timer = None;
|
self.long_timer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Timer(token) => {
|
Event::Timer(_) if self.long_timer.expire(event) => {
|
||||||
if self.long_timer == Some(token) {
|
|
||||||
self.long_timer = None;
|
|
||||||
if matches!(self.state, State::Pressed) {
|
if matches!(self.state, State::Pressed) {
|
||||||
#[cfg(feature = "haptic")]
|
#[cfg(feature = "haptic")]
|
||||||
if self.haptic {
|
if self.haptic {
|
||||||
@ -386,7 +382,6 @@ impl Component for Button {
|
|||||||
return Some(ButtonMsg::LongPressed);
|
return Some(ButtonMsg::LongPressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
None
|
None
|
||||||
|
@ -79,7 +79,6 @@ impl HorizontalSwipe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Frame<T> {
|
pub struct Frame<T> {
|
||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
content: T,
|
content: T,
|
||||||
|
@ -59,7 +59,7 @@ impl AttachAnimation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BUTTON_EXPAND_BORDER: i16 = 32;
|
const BUTTON_EXPAND_BORDER: i16 = 32;
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
title: Label<'static>,
|
title: Label<'static>,
|
||||||
|
@ -163,7 +163,6 @@ impl HoldToConfirmAnim {
|
|||||||
|
|
||||||
/// Component requesting a hold to confirm action from a user. Most typically
|
/// Component requesting a hold to confirm action from a user. Most typically
|
||||||
/// embedded as a content of a Frame.
|
/// embedded as a content of a Frame.
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct HoldToConfirm {
|
pub struct HoldToConfirm {
|
||||||
title: Label<'static>,
|
title: Label<'static>,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
|
@ -5,7 +5,7 @@ use crate::{
|
|||||||
translations::TR,
|
translations::TR,
|
||||||
trezorhal::usb::usb_configured,
|
trezorhal::usb::usb_configured,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx, TimerToken},
|
component::{Component, Event, EventCtx, Timer},
|
||||||
display::{image::ImageInfo, Color, Font},
|
display::{image::ImageInfo, Color, Font},
|
||||||
event::{TouchEvent, USBEvent},
|
event::{TouchEvent, USBEvent},
|
||||||
geometry::{Alignment, Alignment2D, Offset, Point, Rect},
|
geometry::{Alignment, Alignment2D, Offset, Point, Rect},
|
||||||
@ -235,8 +235,8 @@ impl AttachAnimation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct HideLabelAnimation {
|
struct HideLabelAnimation {
|
||||||
pub timer: Stopwatch,
|
pub stopwatch: Stopwatch,
|
||||||
token: TimerToken,
|
timer: Timer,
|
||||||
animating: bool,
|
animating: bool,
|
||||||
hidden: bool,
|
hidden: bool,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
@ -260,8 +260,8 @@ impl HideLabelAnimation {
|
|||||||
|
|
||||||
fn new(label_width: i16) -> Self {
|
fn new(label_width: i16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
timer: Stopwatch::default(),
|
stopwatch: Stopwatch::default(),
|
||||||
token: TimerToken::INVALID,
|
timer: Timer::new(),
|
||||||
animating: false,
|
animating: false,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
duration: Duration::from_millis((label_width as u32 * 300) / 120),
|
duration: Duration::from_millis((label_width as u32 * 300) / 120),
|
||||||
@ -269,19 +269,19 @@ impl HideLabelAnimation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_active(&self) -> bool {
|
fn is_active(&self) -> bool {
|
||||||
self.timer.is_running_within(self.duration)
|
self.stopwatch.is_running_within(self.duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.timer = Stopwatch::default();
|
self.stopwatch = Stopwatch::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elapsed(&self) -> Duration {
|
fn elapsed(&self) -> Duration {
|
||||||
self.timer.elapsed()
|
self.stopwatch.elapsed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_dir(&mut self) {
|
fn change_dir(&mut self) {
|
||||||
let elapsed = self.timer.elapsed();
|
let elapsed = self.stopwatch.elapsed();
|
||||||
|
|
||||||
let start = self
|
let start = self
|
||||||
.duration
|
.duration
|
||||||
@ -289,9 +289,9 @@ impl HideLabelAnimation {
|
|||||||
.and_then(|e| Instant::now().checked_sub(e));
|
.and_then(|e| Instant::now().checked_sub(e));
|
||||||
|
|
||||||
if let Some(start) = start {
|
if let Some(start) = start {
|
||||||
self.timer = Stopwatch::Running(start);
|
self.stopwatch = Stopwatch::Running(start);
|
||||||
} else {
|
} else {
|
||||||
self.timer = Stopwatch::new_started();
|
self.stopwatch = Stopwatch::new_started();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,7 +300,7 @@ impl HideLabelAnimation {
|
|||||||
return Offset::zero();
|
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 {
|
let pos = if self.hidden {
|
||||||
pareen::constant(0.0)
|
pareen::constant(0.0)
|
||||||
@ -329,7 +329,7 @@ impl HideLabelAnimation {
|
|||||||
match event {
|
match event {
|
||||||
Event::Attach(AttachType::Initial) => {
|
Event::Attach(AttachType::Initial) => {
|
||||||
ctx.request_anim_frame();
|
ctx.request_anim_frame();
|
||||||
self.token = ctx.request_timer(Self::HIDE_AFTER);
|
self.timer.start(ctx, Self::HIDE_AFTER);
|
||||||
}
|
}
|
||||||
Event::Attach(AttachType::Resume) => {
|
Event::Attach(AttachType::Resume) => {
|
||||||
self.hidden = resume.hidden;
|
self.hidden = resume.hidden;
|
||||||
@ -341,13 +341,13 @@ impl HideLabelAnimation {
|
|||||||
self.animating = resume.animating;
|
self.animating = resume.animating;
|
||||||
|
|
||||||
if self.animating {
|
if self.animating {
|
||||||
self.timer = Stopwatch::Running(start);
|
self.stopwatch = Stopwatch::Running(start);
|
||||||
ctx.request_anim_frame();
|
ctx.request_anim_frame();
|
||||||
} else {
|
} else {
|
||||||
self.timer = Stopwatch::new_stopped();
|
self.stopwatch = Stopwatch::new_stopped();
|
||||||
}
|
}
|
||||||
if !self.animating && !self.hidden {
|
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) => {
|
Event::Timer(EventCtx::ANIM_FRAME_TIMER) => {
|
||||||
@ -361,28 +361,26 @@ impl HideLabelAnimation {
|
|||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
|
|
||||||
if !self.hidden {
|
if !self.hidden {
|
||||||
self.token = ctx.request_timer(Self::HIDE_AFTER);
|
self.timer.start(ctx, Self::HIDE_AFTER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Timer(token) => {
|
Event::Timer(token) if self.timer.expire(event) && !animation_disabled() => {
|
||||||
if token == self.token && !animation_disabled() {
|
self.stopwatch.start();
|
||||||
self.timer.start();
|
|
||||||
ctx.request_anim_frame();
|
ctx.request_anim_frame();
|
||||||
self.animating = true;
|
self.animating = true;
|
||||||
self.hidden = false;
|
self.hidden = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Event::Touch(TouchEvent::TouchStart(_)) => {
|
Event::Touch(TouchEvent::TouchStart(_)) => {
|
||||||
if !self.animating {
|
if !self.animating {
|
||||||
if self.hidden {
|
if self.hidden {
|
||||||
self.timer.start();
|
self.stopwatch.start();
|
||||||
self.animating = true;
|
self.animating = true;
|
||||||
ctx.request_anim_frame();
|
ctx.request_anim_frame();
|
||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
} else {
|
} else {
|
||||||
self.token = ctx.request_timer(Self::HIDE_AFTER);
|
self.timer.start(ctx, Self::HIDE_AFTER);
|
||||||
}
|
}
|
||||||
} else if !self.hidden {
|
} else if !self.hidden {
|
||||||
self.change_dir();
|
self.change_dir();
|
||||||
@ -399,7 +397,7 @@ impl HideLabelAnimation {
|
|||||||
HideLabelAnimationState {
|
HideLabelAnimationState {
|
||||||
animating: self.animating,
|
animating: self.animating,
|
||||||
hidden: self.hidden,
|
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>>,
|
bg_image: ImageBuffer<Rgb565Canvas<'static>>,
|
||||||
hold_to_lock: bool,
|
hold_to_lock: bool,
|
||||||
loader: Loader,
|
loader: Loader,
|
||||||
delay: Option<TimerToken>,
|
delay: Timer,
|
||||||
attach_animation: AttachAnimation,
|
attach_animation: AttachAnimation,
|
||||||
label_anim: HideLabelAnimation,
|
label_anim: HideLabelAnimation,
|
||||||
}
|
}
|
||||||
@ -458,7 +456,7 @@ impl Homescreen {
|
|||||||
bg_image: buf,
|
bg_image: buf,
|
||||||
hold_to_lock,
|
hold_to_lock,
|
||||||
loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
|
loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
|
||||||
delay: None,
|
delay: Timer::new(),
|
||||||
attach_animation: AttachAnimation::default(),
|
attach_animation: AttachAnimation::default(),
|
||||||
label_anim: HideLabelAnimation::new(label_width),
|
label_anim: HideLabelAnimation::new(label_width),
|
||||||
}
|
}
|
||||||
@ -513,11 +511,11 @@ impl Homescreen {
|
|||||||
if self.loader.is_animating() {
|
if self.loader.is_animating() {
|
||||||
self.loader.start_growing(ctx, Instant::now());
|
self.loader.start_growing(ctx, Instant::now());
|
||||||
} else {
|
} else {
|
||||||
self.delay = Some(ctx.request_timer(LOADER_DELAY));
|
self.delay.start(ctx, LOADER_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Touch(TouchEvent::TouchEnd(_)) => {
|
Event::Touch(TouchEvent::TouchEnd(_)) => {
|
||||||
self.delay = None;
|
self.delay.stop();
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
if self.loader.is_completely_grown(now) {
|
if self.loader.is_completely_grown(now) {
|
||||||
return true;
|
return true;
|
||||||
@ -526,8 +524,7 @@ impl Homescreen {
|
|||||||
self.loader.start_shrinking(ctx, now);
|
self.loader.start_shrinking(ctx, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Timer(token) if Some(token) == self.delay => {
|
Event::Timer(token) if self.delay.expire(event) => {
|
||||||
self.delay = None;
|
|
||||||
self.loader.start_growing(ctx, Instant::now());
|
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> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
self.button_suggestion.event(ctx, event);
|
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)
|
self.on_timeout(ctx)
|
||||||
} else if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) {
|
} else if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) {
|
||||||
self.on_input_click(ctx)
|
self.on_input_click(ctx)
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
ui::{
|
ui::{
|
||||||
component::{text::common::TextEdit, Event, EventCtx, TimerToken},
|
component::{text::common::TextEdit, Event, EventCtx, Timer},
|
||||||
display::{self, Color, Font},
|
display::{self, Color, Font},
|
||||||
geometry::{Alignment2D, Offset, Point, Rect},
|
geometry::{Alignment2D, Offset, Point, Rect},
|
||||||
shape,
|
shape::{self, Renderer},
|
||||||
shape::Renderer,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -17,6 +16,8 @@ pub struct MultiTapKeyboard {
|
|||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
/// The currently pending state.
|
/// The currently pending state.
|
||||||
pending: Option<Pending>,
|
pending: Option<Pending>,
|
||||||
|
/// Timer for clearing the pending state.
|
||||||
|
timer: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Pending {
|
struct Pending {
|
||||||
@ -25,8 +26,6 @@ struct Pending {
|
|||||||
/// Index of the key press (how many times the `key` was pressed, minus
|
/// Index of the key press (how many times the `key` was pressed, minus
|
||||||
/// one).
|
/// one).
|
||||||
press: usize,
|
press: usize,
|
||||||
/// Timer for clearing the pending state.
|
|
||||||
timer: TimerToken,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MultiTapKeyboard {
|
impl MultiTapKeyboard {
|
||||||
@ -35,6 +34,7 @@ impl MultiTapKeyboard {
|
|||||||
Self {
|
Self {
|
||||||
timeout: Duration::from_secs(1),
|
timeout: Duration::from_secs(1),
|
||||||
pending: None,
|
pending: None,
|
||||||
|
timer: Timer::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,21 +48,17 @@ impl MultiTapKeyboard {
|
|||||||
self.pending.as_ref().map(|p| p.press)
|
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
|
/// Returns `true` if `event` is an `Event::Timer` for the currently pending
|
||||||
/// timer.
|
/// timer.
|
||||||
pub fn is_timeout_event(&self, event: Event) -> bool {
|
pub fn timeout_event(&mut self, event: Event) -> bool {
|
||||||
matches!((event, self.pending_timer()), (Event::Timer(t), Some(pt)) if pt == t)
|
self.timer.expire(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset to the empty state. Takes `EventCtx` to request a paint pass (to
|
/// 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
|
/// either hide or show any pending marker our caller might want to draw
|
||||||
/// later).
|
/// later).
|
||||||
pub fn clear_pending_state(&mut self, ctx: &mut EventCtx) {
|
pub fn clear_pending_state(&mut self, ctx: &mut EventCtx) {
|
||||||
|
self.timer.stop();
|
||||||
if self.pending.is_some() {
|
if self.pending.is_some() {
|
||||||
self.pending = None;
|
self.pending = None;
|
||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
@ -97,11 +93,8 @@ impl MultiTapKeyboard {
|
|||||||
// transition only happens as a result of an append op, so the painting should
|
// transition only happens as a result of an append op, so the painting should
|
||||||
// be requested by handling the `TextEdit`.
|
// be requested by handling the `TextEdit`.
|
||||||
self.pending = if key_text.len() > 1 {
|
self.pending = if key_text.len() > 1 {
|
||||||
Some(Pending {
|
self.timer.start(ctx, self.timeout);
|
||||||
key,
|
Some(Pending { key, press })
|
||||||
press,
|
|
||||||
timer: ctx.request_timer(self.timeout),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -321,7 +321,7 @@ impl Component for PassphraseKeyboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
if self.input.multi_tap.is_timeout_event(event) {
|
if self.input.multi_tap.timeout_event(event) {
|
||||||
self.input.multi_tap.clear_pending_state(ctx);
|
self.input.multi_tap.clear_pending_state(ctx);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use crate::{
|
|||||||
component::{
|
component::{
|
||||||
base::{AttachType, ComponentExt},
|
base::{AttachType, ComponentExt},
|
||||||
text::TextStyle,
|
text::TextStyle,
|
||||||
Component, Event, EventCtx, Label, Never, Pad, TimerToken,
|
Component, Event, EventCtx, Label, Never, Pad, Timer,
|
||||||
},
|
},
|
||||||
display::Font,
|
display::Font,
|
||||||
event::TouchEvent,
|
event::TouchEvent,
|
||||||
@ -256,7 +256,7 @@ pub struct PinKeyboard<'a> {
|
|||||||
cancel_btn: Button,
|
cancel_btn: Button,
|
||||||
confirm_btn: Button,
|
confirm_btn: Button,
|
||||||
digit_btns: [(Button, usize); DIGIT_COUNT],
|
digit_btns: [(Button, usize); DIGIT_COUNT],
|
||||||
warning_timer: Option<TimerToken>,
|
warning_timer: Timer,
|
||||||
attach_animation: AttachAnimation,
|
attach_animation: AttachAnimation,
|
||||||
close_animation: CloseAnimation,
|
close_animation: CloseAnimation,
|
||||||
close_confirm: bool,
|
close_confirm: bool,
|
||||||
@ -295,7 +295,7 @@ impl<'a> PinKeyboard<'a> {
|
|||||||
.styled(theme::button_pin_confirm())
|
.styled(theme::button_pin_confirm())
|
||||||
.initially_enabled(false),
|
.initially_enabled(false),
|
||||||
digit_btns: Self::generate_digit_buttons(),
|
digit_btns: Self::generate_digit_buttons(),
|
||||||
warning_timer: None,
|
warning_timer: Timer::new(),
|
||||||
attach_animation: AttachAnimation::default(),
|
attach_animation: AttachAnimation::default(),
|
||||||
close_animation: CloseAnimation::default(),
|
close_animation: CloseAnimation::default(),
|
||||||
close_confirm: false,
|
close_confirm: false,
|
||||||
@ -417,10 +417,10 @@ impl Component for PinKeyboard<'_> {
|
|||||||
match event {
|
match event {
|
||||||
// Set up timer to switch off warning prompt.
|
// Set up timer to switch off warning prompt.
|
||||||
Event::Attach(_) if self.major_warning.is_some() => {
|
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.
|
// 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.major_warning = None;
|
||||||
self.minor_prompt.request_complete_repaint(ctx);
|
self.minor_prompt.request_complete_repaint(ctx);
|
||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
|
@ -106,7 +106,7 @@ impl Component for Slip39Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
if self.multi_tap.is_timeout_event(event) {
|
if self.multi_tap.timeout_event(event) {
|
||||||
// Timeout occurred. Reset the pending key.
|
// Timeout occurred. Reset the pending key.
|
||||||
self.multi_tap.clear_pending_state(ctx);
|
self.multi_tap.clear_pending_state(ctx);
|
||||||
return Some(MnemonicInputMsg::TimedOut);
|
return Some(MnemonicInputMsg::TimedOut);
|
||||||
|
@ -11,7 +11,6 @@ use super::{super::theme, BinarySelection, ButtonContent, HoldToConfirm, TapToCo
|
|||||||
/// - Tap to confirm
|
/// - Tap to confirm
|
||||||
/// - Hold to confirm
|
/// - Hold to confirm
|
||||||
/// - Yes/No selection
|
/// - Yes/No selection
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum PromptScreen {
|
pub enum PromptScreen {
|
||||||
Tap(TapToConfirm),
|
Tap(TapToConfirm),
|
||||||
Hold(HoldToConfirm),
|
Hold(HoldToConfirm),
|
||||||
|
@ -129,7 +129,6 @@ impl StatusAnimation {
|
|||||||
|
|
||||||
/// Component showing status of an operation. Most typically embedded as a
|
/// Component showing status of an operation. Most typically embedded as a
|
||||||
/// content of a Frame and showing success (checkmark with a circle around).
|
/// content of a Frame and showing success (checkmark with a circle around).
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct StatusScreen {
|
pub struct StatusScreen {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
@ -140,7 +139,6 @@ pub struct StatusScreen {
|
|||||||
msg: Label<'static>,
|
msg: Label<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
enum DismissType {
|
enum DismissType {
|
||||||
SwipeUp,
|
SwipeUp,
|
||||||
Timeout(Timeout),
|
Timeout(Timeout),
|
||||||
|
@ -113,7 +113,6 @@ impl TapToConfirmAnim {
|
|||||||
|
|
||||||
/// Component requesting a Tap to confirm action from a user. Most typically
|
/// Component requesting a Tap to confirm action from a user. Most typically
|
||||||
/// embedded as a content of a Frame.
|
/// embedded as a content of a Frame.
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TapToConfirm {
|
pub struct TapToConfirm {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
button: Button,
|
button: Button,
|
||||||
|
@ -167,7 +167,6 @@ impl AttachAnimation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct VerticalMenu {
|
pub struct VerticalMenu {
|
||||||
/// buttons placed vertically from top to bottom
|
/// buttons placed vertically from top to bottom
|
||||||
buttons: VerticalMenuButtons,
|
buttons: VerticalMenuButtons,
|
||||||
|
@ -4,7 +4,7 @@ use super::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
ui::{
|
ui::{
|
||||||
component::{base::Event, Component, EventCtx, Pad, TimerToken},
|
component::{base::Event, Component, EventCtx, Pad, Timer},
|
||||||
event::{ButtonEvent, PhysicalButton},
|
event::{ButtonEvent, PhysicalButton},
|
||||||
geometry::Rect,
|
geometry::Rect,
|
||||||
shape::Renderer,
|
shape::Renderer,
|
||||||
@ -122,7 +122,7 @@ pub struct ButtonContainer {
|
|||||||
/// `ButtonControllerMsg::Triggered`
|
/// `ButtonControllerMsg::Triggered`
|
||||||
long_press_ms: u32,
|
long_press_ms: u32,
|
||||||
/// Timer for sending `ButtonControllerMsg::LongPressed`
|
/// Timer for sending `ButtonControllerMsg::LongPressed`
|
||||||
long_pressed_timer: Option<TimerToken>,
|
long_pressed_timer: Timer,
|
||||||
/// Whether it should even send `ButtonControllerMsg::LongPressed` events
|
/// Whether it should even send `ButtonControllerMsg::LongPressed` events
|
||||||
/// (optional)
|
/// (optional)
|
||||||
send_long_press: bool,
|
send_long_press: bool,
|
||||||
@ -141,7 +141,7 @@ impl ButtonContainer {
|
|||||||
button_type: ButtonType::from_button_details(pos, btn_details),
|
button_type: ButtonType::from_button_details(pos, btn_details),
|
||||||
pressed_since: None,
|
pressed_since: None,
|
||||||
long_press_ms: DEFAULT_LONG_PRESS_MS,
|
long_press_ms: DEFAULT_LONG_PRESS_MS,
|
||||||
long_pressed_timer: None,
|
long_pressed_timer: Timer::new(),
|
||||||
send_long_press,
|
send_long_press,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,7 +190,7 @@ impl ButtonContainer {
|
|||||||
Instant::now().saturating_duration_since(since).to_millis() > self.long_press_ms
|
Instant::now().saturating_duration_since(since).to_millis() > self.long_press_ms
|
||||||
});
|
});
|
||||||
self.pressed_since = None;
|
self.pressed_since = None;
|
||||||
self.long_pressed_timer = None;
|
self.long_pressed_timer.stop();
|
||||||
Some(ButtonControllerMsg::Triggered(self.pos, long_press))
|
Some(ButtonControllerMsg::Triggered(self.pos, long_press))
|
||||||
}
|
}
|
||||||
ButtonType::HoldToConfirm(_) => {
|
ButtonType::HoldToConfirm(_) => {
|
||||||
@ -216,20 +216,20 @@ impl ButtonContainer {
|
|||||||
pub fn got_pressed(&mut self, ctx: &mut EventCtx) {
|
pub fn got_pressed(&mut self, ctx: &mut EventCtx) {
|
||||||
self.pressed_since = Some(Instant::now());
|
self.pressed_since = Some(Instant::now());
|
||||||
if self.send_long_press {
|
if self.send_long_press {
|
||||||
self.long_pressed_timer =
|
self.long_pressed_timer
|
||||||
Some(ctx.request_timer(Duration::from_millis(self.long_press_ms)));
|
.start(ctx, Duration::from_millis(self.long_press_ms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset the pressed information.
|
/// Reset the pressed information.
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.pressed_since = None;
|
self.pressed_since = None;
|
||||||
self.long_pressed_timer = None;
|
self.long_pressed_timer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether token matches what we have
|
/// Whether token matches what we have
|
||||||
pub fn is_timer_token(&self, token: TimerToken) -> bool {
|
pub fn timer_event(&mut self, event: Event) -> bool {
|
||||||
self.long_pressed_timer == Some(token)
|
self.long_pressed_timer.expire(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registering hold event.
|
/// Registering hold event.
|
||||||
@ -380,14 +380,14 @@ impl ButtonController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_long_press_timer_token(&mut self, token: TimerToken) -> Option<ButtonPos> {
|
fn handle_long_press_timers(&mut self, event: Event) -> Option<ButtonPos> {
|
||||||
if self.left_btn.is_timer_token(token) {
|
if self.left_btn.timer_event(event) {
|
||||||
return Some(ButtonPos::Left);
|
return Some(ButtonPos::Left);
|
||||||
}
|
}
|
||||||
if self.middle_btn.is_timer_token(token) {
|
if self.middle_btn.timer_event(event) {
|
||||||
return Some(ButtonPos::Middle);
|
return Some(ButtonPos::Middle);
|
||||||
}
|
}
|
||||||
if self.right_btn.is_timer_token(token) {
|
if self.right_btn.timer_event(event) {
|
||||||
return Some(ButtonPos::Right);
|
return Some(ButtonPos::Right);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -572,11 +572,11 @@ impl Component for ButtonController {
|
|||||||
event
|
event
|
||||||
}
|
}
|
||||||
// Timer - handle clickable properties and HoldToConfirm expiration
|
// Timer - handle clickable properties and HoldToConfirm expiration
|
||||||
Event::Timer(token) => {
|
Event::Timer(_) => {
|
||||||
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
|
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));
|
return Some(ButtonControllerMsg::LongPressed(pos));
|
||||||
}
|
}
|
||||||
self.handle_htc_expiration(ctx, event)
|
self.handle_htc_expiration(ctx, event)
|
||||||
@ -624,9 +624,9 @@ struct IgnoreButtonDelay {
|
|||||||
/// Whether right button is currently clickable
|
/// Whether right button is currently clickable
|
||||||
right_clickable: bool,
|
right_clickable: bool,
|
||||||
/// Timer for setting the left_clickable
|
/// Timer for setting the left_clickable
|
||||||
left_clickable_timer: Option<TimerToken>,
|
left_clickable_timer: Timer,
|
||||||
/// Timer for setting the right_clickable
|
/// Timer for setting the right_clickable
|
||||||
right_clickable_timer: Option<TimerToken>,
|
right_clickable_timer: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IgnoreButtonDelay {
|
impl IgnoreButtonDelay {
|
||||||
@ -635,8 +635,8 @@ impl IgnoreButtonDelay {
|
|||||||
delay: Duration::from_millis(delay_ms),
|
delay: Duration::from_millis(delay_ms),
|
||||||
left_clickable: true,
|
left_clickable: true,
|
||||||
right_clickable: true,
|
right_clickable: true,
|
||||||
left_clickable_timer: None,
|
left_clickable_timer: Timer::new(),
|
||||||
right_clickable_timer: None,
|
right_clickable_timer: Timer::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -644,11 +644,11 @@ impl IgnoreButtonDelay {
|
|||||||
match pos {
|
match pos {
|
||||||
ButtonPos::Left => {
|
ButtonPos::Left => {
|
||||||
self.left_clickable = true;
|
self.left_clickable = true;
|
||||||
self.left_clickable_timer = None;
|
self.left_clickable_timer.stop();
|
||||||
}
|
}
|
||||||
ButtonPos::Right => {
|
ButtonPos::Right => {
|
||||||
self.right_clickable = true;
|
self.right_clickable = true;
|
||||||
self.right_clickable_timer = None;
|
self.right_clickable_timer.stop();
|
||||||
}
|
}
|
||||||
ButtonPos::Middle => {}
|
ButtonPos::Middle => {}
|
||||||
}
|
}
|
||||||
@ -656,10 +656,10 @@ impl IgnoreButtonDelay {
|
|||||||
|
|
||||||
pub fn handle_button_press(&mut self, ctx: &mut EventCtx, button: PhysicalButton) {
|
pub fn handle_button_press(&mut self, ctx: &mut EventCtx, button: PhysicalButton) {
|
||||||
if matches!(button, PhysicalButton::Left) {
|
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) {
|
if matches!(button, PhysicalButton::Right) {
|
||||||
self.left_clickable_timer = Some(ctx.request_timer(self.delay));
|
self.left_clickable_timer.start(ctx, self.delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,22 +673,20 @@ impl IgnoreButtonDelay {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_timer_token(&mut self, token: TimerToken) {
|
pub fn handle_timers(&mut self, event: Event) {
|
||||||
if self.left_clickable_timer == Some(token) {
|
if self.left_clickable_timer.expire(event) {
|
||||||
self.left_clickable = false;
|
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 = false;
|
||||||
self.right_clickable_timer = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.left_clickable = true;
|
self.left_clickable = true;
|
||||||
self.right_clickable = true;
|
self.right_clickable = true;
|
||||||
self.left_clickable_timer = None;
|
self.left_clickable_timer.stop();
|
||||||
self.right_clickable_timer = None;
|
self.right_clickable_timer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,7 +698,7 @@ impl IgnoreButtonDelay {
|
|||||||
/// Can be started e.g. by holding left/right button.
|
/// Can be started e.g. by holding left/right button.
|
||||||
pub struct AutomaticMover {
|
pub struct AutomaticMover {
|
||||||
/// For requesting timer events repeatedly
|
/// For requesting timer events repeatedly
|
||||||
timer_token: Option<TimerToken>,
|
timer: Timer,
|
||||||
/// Which direction should we go (which button is down)
|
/// Which direction should we go (which button is down)
|
||||||
moving_direction: Option<ButtonPos>,
|
moving_direction: Option<ButtonPos>,
|
||||||
/// How many screens were moved automatically
|
/// How many screens were moved automatically
|
||||||
@ -721,7 +719,7 @@ impl AutomaticMover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
timer_token: None,
|
timer: Timer::new(),
|
||||||
moving_direction: None,
|
moving_direction: None,
|
||||||
auto_moved_screens: 0,
|
auto_moved_screens: 0,
|
||||||
duration_func: default_duration_func,
|
duration_func: default_duration_func,
|
||||||
@ -760,12 +758,12 @@ impl AutomaticMover {
|
|||||||
pub fn start_moving(&mut self, ctx: &mut EventCtx, button: ButtonPos) {
|
pub fn start_moving(&mut self, ctx: &mut EventCtx, button: ButtonPos) {
|
||||||
self.auto_moved_screens = 0;
|
self.auto_moved_screens = 0;
|
||||||
self.moving_direction = Some(button);
|
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) {
|
pub fn stop_moving(&mut self) {
|
||||||
self.moving_direction = None;
|
self.moving_direction = None;
|
||||||
self.timer_token = None;
|
self.timer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,16 +779,12 @@ impl Component for AutomaticMover {
|
|||||||
fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {}
|
fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {}
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
// Moving automatically only when we receive a TimerToken that we have
|
if self.timer.expire(event) && self.moving_direction.is_some() {
|
||||||
// requested before
|
// Restart timer and send the appropriate button trigger event
|
||||||
if let Event::Timer(token) = event {
|
self.timer.start(ctx, self.get_auto_move_duration());
|
||||||
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;
|
self.auto_moved_screens += 1;
|
||||||
return self.moving_direction;
|
return self.moving_direction;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,12 @@ use crate::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
Component, ComponentExt, Event, EventCtx, FixedHeightBar, MsgMap, Split, TimerToken,
|
Component, ComponentExt, Event, EventCtx, FixedHeightBar, MsgMap, Split, Timer,
|
||||||
},
|
},
|
||||||
display::{self, toif::Icon, Color, Font},
|
display::{self, toif::Icon, Color, Font},
|
||||||
event::TouchEvent,
|
event::TouchEvent,
|
||||||
geometry::{Alignment2D, Insets, Offset, Point, Rect},
|
geometry::{Alignment2D, Insets, Offset, Point, Rect},
|
||||||
shape,
|
shape::{self, Renderer},
|
||||||
shape::Renderer,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ pub struct Button {
|
|||||||
styles: ButtonStyleSheet,
|
styles: ButtonStyleSheet,
|
||||||
state: State,
|
state: State,
|
||||||
long_press: Option<Duration>,
|
long_press: Option<Duration>,
|
||||||
long_timer: Option<TimerToken>,
|
long_timer: Timer,
|
||||||
haptics: bool,
|
haptics: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +47,7 @@ impl Button {
|
|||||||
styles: theme::button_default(),
|
styles: theme::button_default(),
|
||||||
state: State::Initial,
|
state: State::Initial,
|
||||||
long_press: None,
|
long_press: None,
|
||||||
long_timer: None,
|
long_timer: Timer::new(),
|
||||||
haptics: true,
|
haptics: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,7 +316,7 @@ impl Component for Button {
|
|||||||
}
|
}
|
||||||
self.set(ctx, State::Pressed);
|
self.set(ctx, State::Pressed);
|
||||||
if let Some(duration) = self.long_press {
|
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);
|
return Some(ButtonMsg::Pressed);
|
||||||
}
|
}
|
||||||
@ -349,13 +348,11 @@ impl Component for Button {
|
|||||||
_ => {
|
_ => {
|
||||||
// Touch finished outside our area.
|
// Touch finished outside our area.
|
||||||
self.set(ctx, State::Initial);
|
self.set(ctx, State::Initial);
|
||||||
self.long_timer = None;
|
self.long_timer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Timer(token) => {
|
Event::Timer(_) if self.long_timer.expire(event) => {
|
||||||
if self.long_timer == Some(token) {
|
|
||||||
self.long_timer = None;
|
|
||||||
if matches!(self.state, State::Pressed) {
|
if matches!(self.state, State::Pressed) {
|
||||||
#[cfg(feature = "haptic")]
|
#[cfg(feature = "haptic")]
|
||||||
if self.haptics {
|
if self.haptics {
|
||||||
@ -365,7 +362,6 @@ impl Component for Button {
|
|||||||
return Some(ButtonMsg::LongPressed);
|
return Some(ButtonMsg::LongPressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
None
|
None
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
translations::TR,
|
translations::TR,
|
||||||
trezorhal::usb::usb_configured,
|
trezorhal::usb::usb_configured,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx, Pad, TimerToken},
|
component::{Component, Event, EventCtx, Pad, Timer},
|
||||||
display::{
|
display::{
|
||||||
self,
|
self,
|
||||||
image::{ImageInfo, ToifFormat},
|
image::{ImageInfo, ToifFormat},
|
||||||
@ -58,7 +58,7 @@ pub struct Homescreen {
|
|||||||
loader: Loader,
|
loader: Loader,
|
||||||
pad: Pad,
|
pad: Pad,
|
||||||
paint_notification_only: bool,
|
paint_notification_only: bool,
|
||||||
delay: Option<TimerToken>,
|
delay: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum HomescreenMsg {
|
pub enum HomescreenMsg {
|
||||||
@ -79,7 +79,7 @@ impl Homescreen {
|
|||||||
loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
|
loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
|
||||||
pad: Pad::with_background(theme::BG),
|
pad: Pad::with_background(theme::BG),
|
||||||
paint_notification_only: false,
|
paint_notification_only: false,
|
||||||
delay: None,
|
delay: Timer::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,11 +152,11 @@ impl Homescreen {
|
|||||||
if self.loader.is_animating() {
|
if self.loader.is_animating() {
|
||||||
self.loader.start_growing(ctx, Instant::now());
|
self.loader.start_growing(ctx, Instant::now());
|
||||||
} else {
|
} else {
|
||||||
self.delay = Some(ctx.request_timer(LOADER_DELAY));
|
self.delay.start(ctx, LOADER_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Touch(TouchEvent::TouchEnd(_)) => {
|
Event::Touch(TouchEvent::TouchEnd(_)) => {
|
||||||
self.delay = None;
|
self.delay.stop();
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
if self.loader.is_completely_grown(now) {
|
if self.loader.is_completely_grown(now) {
|
||||||
return true;
|
return true;
|
||||||
@ -165,8 +165,7 @@ impl Homescreen {
|
|||||||
self.loader.start_shrinking(ctx, now);
|
self.loader.start_shrinking(ctx, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Timer(token) if Some(token) == self.delay => {
|
Event::Timer(_) if self.delay.expire(event) => {
|
||||||
self.delay = None;
|
|
||||||
self.pad.clear();
|
self.pad.clear();
|
||||||
self.paint_notification_only = false;
|
self.paint_notification_only = false;
|
||||||
self.loader.start_growing(ctx, Instant::now());
|
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> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
self.button_suggestion.event(ctx, event);
|
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)
|
self.on_timeout(ctx)
|
||||||
} else if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) {
|
} else if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) {
|
||||||
self.on_input_click(ctx)
|
self.on_input_click(ctx)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
ui::{
|
ui::{
|
||||||
component::{text::common::TextEdit, Event, EventCtx, TimerToken},
|
component::{text::common::TextEdit, Event, EventCtx, Timer},
|
||||||
display::{self, Color, Font},
|
display::{self, Color, Font},
|
||||||
geometry::{Offset, Point, Rect},
|
geometry::{Offset, Point, Rect},
|
||||||
shape,
|
shape,
|
||||||
@ -24,7 +24,16 @@ struct Pending {
|
|||||||
/// one).
|
/// one).
|
||||||
press: usize,
|
press: usize,
|
||||||
/// Timer for clearing the pending state.
|
/// 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 {
|
impl MultiTapKeyboard {
|
||||||
@ -46,15 +55,10 @@ impl MultiTapKeyboard {
|
|||||||
self.pending.as_ref().map(|p| p.press)
|
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
|
/// Returns `true` if `event` is an `Event::Timer` for the currently pending
|
||||||
/// timer.
|
/// timer.
|
||||||
pub fn is_timeout_event(&self, event: Event) -> bool {
|
pub fn timeout_event(&mut self, event: Event) -> bool {
|
||||||
matches!((event, self.pending_timer()), (Event::Timer(t), Some(pt)) if pt == t)
|
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
|
/// 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
|
// transition only happens as a result of an append op, so the painting should
|
||||||
// be requested by handling the `TextEdit`.
|
// be requested by handling the `TextEdit`.
|
||||||
self.pending = if key_text.len() > 1 {
|
self.pending = if key_text.len() > 1 {
|
||||||
Some(Pending {
|
Some(Pending::start(ctx, key, press, self.timeout))
|
||||||
key,
|
|
||||||
press,
|
|
||||||
timer: ctx.request_timer(self.timeout),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -221,9 +221,15 @@ impl Component for PassphraseKeyboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
if self.input.inner().multi_tap.is_timeout_event(event) {
|
let multitap_timeout = self.input.mutate(ctx, |ctx, i| {
|
||||||
self.input
|
if i.multi_tap.timeout_event(event) {
|
||||||
.mutate(ctx, |ctx, i| i.multi_tap.clear_pending_state(ctx));
|
i.multi_tap.clear_pending_state(ctx);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if multitap_timeout {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if let Some(swipe) = self.page_swipe.event(ctx, event) {
|
if let Some(swipe) = self.page_swipe.event(ctx, event) {
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
base::ComponentExt, text::TextStyle, Child, Component, Event, EventCtx, Label, Maybe,
|
base::ComponentExt, text::TextStyle, Child, Component, Event, EventCtx, Label, Maybe,
|
||||||
Never, Pad, TimerToken,
|
Never, Pad, Timer,
|
||||||
},
|
},
|
||||||
display::{self, Font},
|
display::{self, Font},
|
||||||
event::TouchEvent,
|
event::TouchEvent,
|
||||||
@ -54,7 +54,7 @@ pub struct PinKeyboard<'a> {
|
|||||||
cancel_btn: Child<Maybe<Button>>,
|
cancel_btn: Child<Maybe<Button>>,
|
||||||
confirm_btn: Child<Button>,
|
confirm_btn: Child<Button>,
|
||||||
digit_btns: [Child<Button>; DIGIT_COUNT],
|
digit_btns: [Child<Button>; DIGIT_COUNT],
|
||||||
warning_timer: Option<TimerToken>,
|
warning_timer: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PinKeyboard<'a> {
|
impl<'a> PinKeyboard<'a> {
|
||||||
@ -99,7 +99,7 @@ impl<'a> PinKeyboard<'a> {
|
|||||||
.initially_enabled(false)
|
.initially_enabled(false)
|
||||||
.into_child(),
|
.into_child(),
|
||||||
digit_btns: Self::generate_digit_buttons(),
|
digit_btns: Self::generate_digit_buttons(),
|
||||||
warning_timer: None,
|
warning_timer: Timer::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,10 +201,10 @@ impl Component for PinKeyboard<'_> {
|
|||||||
match event {
|
match event {
|
||||||
// Set up timer to switch off warning prompt.
|
// Set up timer to switch off warning prompt.
|
||||||
Event::Attach(_) if self.major_warning.is_some() => {
|
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.
|
// 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.major_warning = None;
|
||||||
self.textbox_pad.clear();
|
self.textbox_pad.clear();
|
||||||
self.minor_prompt.request_complete_repaint(ctx);
|
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> {
|
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.
|
// Timeout occurred. Reset the pending key.
|
||||||
self.multi_tap.clear_pending_state(ctx);
|
self.multi_tap.clear_pending_state(ctx);
|
||||||
return Some(MnemonicInputMsg::TimedOut);
|
return Some(MnemonicInputMsg::TimedOut);
|
||||||
|
@ -360,11 +360,13 @@ class Layout(Generic[T]):
|
|||||||
# do not schedule another animation frame if one is already scheduled
|
# do not schedule another animation frame if one is already scheduled
|
||||||
return
|
return
|
||||||
|
|
||||||
assert token not in self.timers
|
task = self.timers.get(token)
|
||||||
|
if task is None:
|
||||||
task = timer_task()
|
task = timer_task()
|
||||||
self.timers[token] = task
|
self.timers[token] = task
|
||||||
|
|
||||||
deadline = utime.ticks_add(utime.ticks_ms(), duration_ms)
|
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:
|
def _emit_message(self, msg: Any) -> None:
|
||||||
"""Process a message coming out of the Rust layout. Set is as a result and shut
|
"""Process a message coming out of the Rust layout. Set is as a result and shut
|
||||||
|
Loading…
Reference in New Issue
Block a user