1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-03 12:28:13 +00:00

refactor(core): optimize repeated code for swipe directions

This commit is contained in:
matejcik 2024-06-04 16:07:09 +02:00 committed by matejcik
parent fbfb000d62
commit d1cf36097a
4 changed files with 114 additions and 132 deletions

View File

@ -22,6 +22,38 @@ impl SwipeDirection {
SwipeDirection::Right => Offset::x(size.x), SwipeDirection::Right => Offset::x(size.x),
} }
} }
pub fn iter() -> SwipeDirectionIterator {
SwipeDirectionIterator::new()
}
}
pub struct SwipeDirectionIterator {
current: Option<SwipeDirection>,
}
impl SwipeDirectionIterator {
pub fn new() -> Self {
SwipeDirectionIterator {
current: Some(SwipeDirection::Up),
}
}
}
impl Iterator for SwipeDirectionIterator {
type Item = SwipeDirection;
fn next(&mut self) -> Option<Self::Item> {
let next_state = match self.current {
Some(SwipeDirection::Up) => Some(SwipeDirection::Down),
Some(SwipeDirection::Down) => Some(SwipeDirection::Left),
Some(SwipeDirection::Left) => Some(SwipeDirection::Right),
Some(SwipeDirection::Right) => None,
None => None,
};
core::mem::replace(&mut self.current, next_state)
}
} }
#[derive(Clone)] #[derive(Clone)]

View File

@ -63,6 +63,36 @@ impl SwipeConfig {
self[dir].is_some() self[dir].is_some()
} }
/// Calculate how much progress over `threshold` was made in the swipe
/// direction.
///
/// If the swipe direction is not allowed, this will return 0.
pub fn progress(&self, dir: SwipeDirection, movement: Offset, threshold: u16) -> u16 {
if !self.is_allowed(dir) {
return 0;
}
let correct_movement = match dir {
SwipeDirection::Right => movement.x > 0,
SwipeDirection::Left => movement.x < 0,
SwipeDirection::Down => movement.y > 0,
SwipeDirection::Up => movement.y < 0,
};
if !correct_movement {
return 0;
}
let movement = movement.abs();
match dir {
SwipeDirection::Right => (movement.x as u16).saturating_sub(threshold),
SwipeDirection::Left => (movement.x as u16).saturating_sub(threshold),
SwipeDirection::Down => (movement.y as u16).saturating_sub(threshold),
SwipeDirection::Up => (movement.y as u16).saturating_sub(threshold),
}
}
pub fn duration(&self, dir: SwipeDirection) -> Option<Duration> { pub fn duration(&self, dir: SwipeDirection) -> Option<Duration> {
self[dir].as_ref().map(|s| s.duration) self[dir].as_ref().map(|s| s.duration)
} }
@ -111,7 +141,7 @@ impl core::ops::IndexMut<SwipeDirection> for SwipeConfig {
#[derive(Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq)]
pub enum SwipeDetectMsg { pub enum SwipeDetectMsg {
Move(SwipeDirection, i16), Move(SwipeDirection, u16),
Trigger(SwipeDirection), Trigger(SwipeDirection),
} }
@ -119,17 +149,20 @@ pub struct SwipeDetect {
origin: Option<Point>, origin: Option<Point>,
locked: Option<SwipeDirection>, locked: Option<SwipeDirection>,
final_animation: Option<Animation<i16>>, final_animation: Option<Animation<i16>>,
moved: i16, moved: u16,
} }
impl SwipeDetect { impl SwipeDetect {
const DISTANCE: i16 = 120; const DISTANCE: u16 = 120;
pub const PROGRESS_MAX: i16 = 1000; pub const PROGRESS_MAX: i16 = 1000;
const DURATION_MS: u32 = 333; const DURATION_MS: u32 = 333;
const TRIGGER_THRESHOLD: f32 = 0.3; const TRIGGER_THRESHOLD: f32 = 0.3;
const DETECT_THRESHOLD: f32 = 0.1; const DETECT_THRESHOLD: f32 = 0.1;
const MIN_LOCK: u16 = (Self::DISTANCE as f32 * Self::DETECT_THRESHOLD) as u16;
const MIN_TRIGGER: u16 = (Self::DISTANCE as f32 * Self::TRIGGER_THRESHOLD) as u16;
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
origin: None, origin: None,
@ -139,16 +172,16 @@ impl SwipeDetect {
} }
} }
fn min_lock(&self) -> i16 { const fn min_lock(&self) -> u16 {
(Self::DISTANCE as f32 * Self::DETECT_THRESHOLD) as i16 Self::MIN_LOCK
} }
fn min_trigger(&self) -> i16 { const fn min_trigger(&self) -> u16 {
(Self::DISTANCE as f32 * Self::TRIGGER_THRESHOLD) as i16 Self::MIN_TRIGGER
} }
fn progress(&self, val: i16) -> i16 { fn progress(&self, val: u16) -> u16 {
((val.max(0) as f32 / Self::DISTANCE as f32) * Self::PROGRESS_MAX as f32) as i16 ((val as f32 / Self::DISTANCE as f32) * Self::PROGRESS_MAX as f32) as u16
} }
pub fn trigger(&mut self, ctx: &mut EventCtx, dir: SwipeDirection, config: SwipeConfig) { pub fn trigger(&mut self, ctx: &mut EventCtx, dir: SwipeDirection, config: SwipeConfig) {
@ -191,87 +224,31 @@ impl SwipeDetect {
// Compare the touch distance with our allowed directions and determine if it // Compare the touch distance with our allowed directions and determine if it
// constitutes a valid swipe. // constitutes a valid swipe.
let ofs = pos - origin; let ofs = pos - origin;
let ofs_min = ofs.abs() - Offset::new(self.min_lock(), self.min_lock());
let mut res = None; let res = match self.locked {
if self.locked.is_none() { Some(locked) => {
if ofs.x > 0 && ofs_min.x > 0 && config.is_allowed(SwipeDirection::Right) { // advance in locked direction only
self.locked = Some(SwipeDirection::Right); let moved = config.progress(locked, ofs, self.min_lock());
res = Some(SwipeDetectMsg::Move( Some(SwipeDetectMsg::Move(locked, self.progress(moved)))
SwipeDirection::Right,
self.progress(ofs_min.x),
));
} }
if ofs.x < 0 && ofs_min.x > 0 && config.is_allowed(SwipeDirection::Left) { None => {
self.locked = Some(SwipeDirection::Left); let mut res = None;
res = Some(SwipeDetectMsg::Move( for dir in SwipeDirection::iter() {
SwipeDirection::Left, let progress = config.progress(dir, ofs, self.min_lock());
self.progress(ofs_min.x), if progress > 0 {
)); self.locked = Some(dir);
} res = Some(SwipeDetectMsg::Move(dir, self.progress(progress)));
if ofs.y < 0 && ofs_min.y > 0 && config.is_allowed(SwipeDirection::Up) { break;
self.locked = Some(SwipeDirection::Up);
res = Some(SwipeDetectMsg::Move(
SwipeDirection::Up,
self.progress(ofs_min.y),
));
}
if ofs.y > 0 && ofs_min.y > 0 && config.is_allowed(SwipeDirection::Down) {
self.locked = Some(SwipeDirection::Down);
res = Some(SwipeDetectMsg::Move(
SwipeDirection::Down,
self.progress(ofs_min.y),
));
}
} else {
res = match self.locked.unwrap() {
SwipeDirection::Left => {
if ofs.x > 0 {
Some(SwipeDetectMsg::Move(SwipeDirection::Left, 0))
} else {
Some(SwipeDetectMsg::Move(
SwipeDirection::Left,
self.progress(ofs_min.x),
))
} }
} }
SwipeDirection::Right => { res
if ofs.x < 0 { }
Some(SwipeDetectMsg::Move(SwipeDirection::Right, 0)) };
} else {
Some(SwipeDetectMsg::Move(
SwipeDirection::Right,
self.progress(ofs_min.x),
))
}
}
SwipeDirection::Up => {
if ofs.y > 0 {
Some(SwipeDetectMsg::Move(SwipeDirection::Up, 0))
} else {
Some(SwipeDetectMsg::Move(
SwipeDirection::Up,
self.progress(ofs_min.y),
))
}
}
SwipeDirection::Down => {
if ofs.y < 0 {
Some(SwipeDetectMsg::Move(SwipeDirection::Down, 0))
} else {
Some(SwipeDetectMsg::Move(
SwipeDirection::Down,
self.progress(ofs_min.y),
))
}
}
};
}
// Todo trigger an action if distance is met // Todo trigger an action if distance is met
if let Some(SwipeDetectMsg::Move(_, ofs)) = res { if let Some(SwipeDetectMsg::Move(_, progress)) = res {
self.moved = ofs; self.moved = progress;
} }
if animation_disabled() { if animation_disabled() {
@ -289,27 +266,32 @@ impl SwipeDetect {
// Compare the touch distance with our allowed directions and determine if it // Compare the touch distance with our allowed directions and determine if it
// constitutes a valid swipe. // constitutes a valid swipe.
let ofs = pos - origin; let ofs = pos - origin;
let ofs_min = ofs.abs() - Offset::new(self.min_trigger(), self.min_trigger());
match self.locked { let final_value = match self.locked {
// advance in locked direction only // advance in locked direction only trigger animation towards ending
Some(locked) if config.progress(locked, ofs, 0) > 0 => (), // position
// advance in direction other than locked clears the lock -- touch ends Some(locked) if config.progress(locked, ofs, self.min_trigger()) > 0 => {
// without triggering Self::PROGRESS_MAX
Some(_) => self.locked = None, }
// advance in direction other than locked trigger animation towards starting
// position
Some(_) => 0,
None => { None => {
let mut res = 0;
for dir in SwipeDirection::iter() { for dir in SwipeDirection::iter() {
// insta-lock if the movement went at least the trigger distance // insta-lock if the movement went at least the trigger distance
if config.progress(dir, ofs, self.min_trigger()) > 0 { if config.progress(dir, ofs, self.min_trigger()) > 0 {
self.locked = Some(dir); self.locked = Some(dir);
break; res = Self::PROGRESS_MAX;
} }
} }
res
} }
}; };
let Some(locked) = self.locked else { let Some(locked) = self.locked else {
// No direction is locked. Touch ended without triggering a swipe. // Touch ended without triggering a swipe.
return None; return None;
}; };
@ -327,7 +309,7 @@ impl SwipeDetect {
let duration = ((duration.to_millis() as f32 * ratio) as u32).max(0); let duration = ((duration.to_millis() as f32 * ratio) as u32).max(0);
self.final_animation = Some(Animation::new( self.final_animation = Some(Animation::new(
self.moved as i16, self.moved as i16,
Self::PROGRESS_MAX, final_value,
Duration::from_millis(duration), Duration::from_millis(duration),
Instant::now(), Instant::now(),
)); ));
@ -338,51 +320,19 @@ impl SwipeDetect {
self.locked = None; self.locked = None;
return Some(SwipeDetectMsg::Trigger(locked)); return Some(SwipeDetectMsg::Trigger(locked));
} }
if finalize {
if !animation_disabled() {
ctx.request_anim_frame();
ctx.request_paint();
let done = self.moved as f32 / Self::PROGRESS_MAX as f32;
let ratio = 1.0 - done;
let duration = config
.duration(self.locked.unwrap())
.unwrap_or(Duration::from_millis(Self::DURATION_MS));
let duration = ((duration.to_millis() as f32 * ratio) as u32).max(0);
self.final_animation = Some(Animation::new(
self.moved,
final_value,
Duration::from_millis(duration),
Instant::now(),
));
} else {
ctx.request_anim_frame();
ctx.request_paint();
self.final_animation = None;
self.moved = 0;
let locked = self.locked.take();
if final_value != 0 {
return Some(SwipeDetectMsg::Trigger(locked.unwrap()));
}
}
}
return None; return None;
} }
} }
(Event::Timer(EventCtx::ANIM_FRAME_TIMER), _) => { (Event::Timer(EventCtx::ANIM_FRAME_TIMER), _) => {
if self.locked.is_some() { if let Some(locked) = self.locked {
let mut finish = false; let mut finish = false;
let res = if let Some(animation) = &self.final_animation { let res = if let Some(animation) = &self.final_animation {
if animation.finished(Instant::now()) { if animation.finished(Instant::now()) {
finish = true; finish = true;
if animation.to != 0 { if animation.to != 0 {
Some(SwipeDetectMsg::Trigger(self.locked.unwrap())) Some(SwipeDetectMsg::Trigger(locked))
} else { } else {
Some(SwipeDetectMsg::Move(self.locked.unwrap(), 0)) Some(SwipeDetectMsg::Move(locked, 0))
} }
} else { } else {
ctx.request_anim_frame(); ctx.request_anim_frame();
@ -391,8 +341,8 @@ impl SwipeDetect {
None None
} else { } else {
Some(SwipeDetectMsg::Move( Some(SwipeDetectMsg::Move(
self.locked.unwrap(), locked,
animation.value(Instant::now()), animation.value(Instant::now()).max(0) as u16,
)) ))
} }
} }

View File

@ -170,7 +170,7 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
Some(Event::Swipe(SwipeEvent::End(dir))) Some(Event::Swipe(SwipeEvent::End(dir)))
} }
Some(SwipeDetectMsg::Move(dir, progress)) => { Some(SwipeDetectMsg::Move(dir, progress)) => {
Some(Event::Swipe(SwipeEvent::Move(dir, progress))) Some(Event::Swipe(SwipeEvent::Move(dir, progress as i16)))
} }
_ => Some(event), _ => Some(event),
} }

View File

@ -49,7 +49,7 @@ impl<T: Swipable + Component> Component for SwipeUpScreen<T> {
return Some(SwipeUpScreenMsg::Swiped); return Some(SwipeUpScreenMsg::Swiped);
} }
Some(SwipeDetectMsg::Move(dir, progress)) => { Some(SwipeDetectMsg::Move(dir, progress)) => {
Event::Swipe(SwipeEvent::Move(dir, progress)) Event::Swipe(SwipeEvent::Move(dir, progress as i16))
} }
_ => event, _ => event,
}; };