diff --git a/core/embed/rust/src/ui/component/swipe.rs b/core/embed/rust/src/ui/component/swipe.rs index ece3d22b6b..b614d7d7eb 100644 --- a/core/embed/rust/src/ui/component/swipe.rs +++ b/core/embed/rust/src/ui/component/swipe.rs @@ -22,6 +22,38 @@ impl SwipeDirection { SwipeDirection::Right => Offset::x(size.x), } } + + pub fn iter() -> SwipeDirectionIterator { + SwipeDirectionIterator::new() + } +} + +pub struct SwipeDirectionIterator { + current: Option, +} + +impl SwipeDirectionIterator { + pub fn new() -> Self { + SwipeDirectionIterator { + current: Some(SwipeDirection::Up), + } + } +} + +impl Iterator for SwipeDirectionIterator { + type Item = SwipeDirection; + + fn next(&mut self) -> Option { + 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)] diff --git a/core/embed/rust/src/ui/component/swipe_detect.rs b/core/embed/rust/src/ui/component/swipe_detect.rs index 6bcac5cae7..435f1f7d1e 100644 --- a/core/embed/rust/src/ui/component/swipe_detect.rs +++ b/core/embed/rust/src/ui/component/swipe_detect.rs @@ -63,6 +63,36 @@ impl SwipeConfig { 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 { self[dir].as_ref().map(|s| s.duration) } @@ -111,7 +141,7 @@ impl core::ops::IndexMut for SwipeConfig { #[derive(Copy, Clone, Eq, PartialEq)] pub enum SwipeDetectMsg { - Move(SwipeDirection, i16), + Move(SwipeDirection, u16), Trigger(SwipeDirection), } @@ -119,17 +149,20 @@ pub struct SwipeDetect { origin: Option, locked: Option, final_animation: Option>, - moved: i16, + moved: u16, } impl SwipeDetect { - const DISTANCE: i16 = 120; + const DISTANCE: u16 = 120; pub const PROGRESS_MAX: i16 = 1000; const DURATION_MS: u32 = 333; const TRIGGER_THRESHOLD: f32 = 0.3; 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 { Self { origin: None, @@ -139,16 +172,16 @@ impl SwipeDetect { } } - fn min_lock(&self) -> i16 { - (Self::DISTANCE as f32 * Self::DETECT_THRESHOLD) as i16 + const fn min_lock(&self) -> u16 { + Self::MIN_LOCK } - fn min_trigger(&self) -> i16 { - (Self::DISTANCE as f32 * Self::TRIGGER_THRESHOLD) as i16 + const fn min_trigger(&self) -> u16 { + Self::MIN_TRIGGER } - fn progress(&self, val: i16) -> i16 { - ((val.max(0) as f32 / Self::DISTANCE as f32) * Self::PROGRESS_MAX as f32) as i16 + fn progress(&self, val: u16) -> u16 { + ((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) { @@ -191,87 +224,31 @@ impl SwipeDetect { // Compare the touch distance with our allowed directions and determine if it // constitutes a valid swipe. let ofs = pos - origin; - let ofs_min = ofs.abs() - Offset::new(self.min_lock(), self.min_lock()); - let mut res = None; - if self.locked.is_none() { - if ofs.x > 0 && ofs_min.x > 0 && config.is_allowed(SwipeDirection::Right) { - self.locked = Some(SwipeDirection::Right); - res = Some(SwipeDetectMsg::Move( - SwipeDirection::Right, - self.progress(ofs_min.x), - )); + let res = match self.locked { + Some(locked) => { + // advance in locked direction only + let moved = config.progress(locked, ofs, self.min_lock()); + Some(SwipeDetectMsg::Move(locked, self.progress(moved))) } - if ofs.x < 0 && ofs_min.x > 0 && config.is_allowed(SwipeDirection::Left) { - self.locked = Some(SwipeDirection::Left); - res = Some(SwipeDetectMsg::Move( - SwipeDirection::Left, - self.progress(ofs_min.x), - )); - } - if ofs.y < 0 && ofs_min.y > 0 && config.is_allowed(SwipeDirection::Up) { - 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), - )) + None => { + let mut res = None; + for dir in SwipeDirection::iter() { + let progress = config.progress(dir, ofs, self.min_lock()); + if progress > 0 { + self.locked = Some(dir); + res = Some(SwipeDetectMsg::Move(dir, self.progress(progress))); + break; } } - SwipeDirection::Right => { - 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), - )) - } - } - }; - } + res + } + }; // Todo trigger an action if distance is met - if let Some(SwipeDetectMsg::Move(_, ofs)) = res { - self.moved = ofs; + if let Some(SwipeDetectMsg::Move(_, progress)) = res { + self.moved = progress; } if animation_disabled() { @@ -289,27 +266,32 @@ impl SwipeDetect { // Compare the touch distance with our allowed directions and determine if it // constitutes a valid swipe. let ofs = pos - origin; - let ofs_min = ofs.abs() - Offset::new(self.min_trigger(), self.min_trigger()); - match self.locked { - // advance in locked direction only - Some(locked) if config.progress(locked, ofs, 0) > 0 => (), - // advance in direction other than locked clears the lock -- touch ends - // without triggering - Some(_) => self.locked = None, + let final_value = match self.locked { + // advance in locked direction only trigger animation towards ending + // position + Some(locked) if config.progress(locked, ofs, self.min_trigger()) > 0 => { + Self::PROGRESS_MAX + } + // advance in direction other than locked trigger animation towards starting + // position + Some(_) => 0, None => { + let mut res = 0; for dir in SwipeDirection::iter() { // insta-lock if the movement went at least the trigger distance if config.progress(dir, ofs, self.min_trigger()) > 0 { self.locked = Some(dir); - break; + res = Self::PROGRESS_MAX; } } + + res } }; let Some(locked) = self.locked else { - // No direction is locked. Touch ended without triggering a swipe. + // Touch ended without triggering a swipe. return None; }; @@ -327,7 +309,7 @@ impl SwipeDetect { let duration = ((duration.to_millis() as f32 * ratio) as u32).max(0); self.final_animation = Some(Animation::new( self.moved as i16, - Self::PROGRESS_MAX, + final_value, Duration::from_millis(duration), Instant::now(), )); @@ -338,51 +320,19 @@ impl SwipeDetect { self.locked = None; 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; } } (Event::Timer(EventCtx::ANIM_FRAME_TIMER), _) => { - if self.locked.is_some() { + if let Some(locked) = self.locked { let mut finish = false; let res = if let Some(animation) = &self.final_animation { if animation.finished(Instant::now()) { finish = true; if animation.to != 0 { - Some(SwipeDetectMsg::Trigger(self.locked.unwrap())) + Some(SwipeDetectMsg::Trigger(locked)) } else { - Some(SwipeDetectMsg::Move(self.locked.unwrap(), 0)) + Some(SwipeDetectMsg::Move(locked, 0)) } } else { ctx.request_anim_frame(); @@ -391,8 +341,8 @@ impl SwipeDetect { None } else { Some(SwipeDetectMsg::Move( - self.locked.unwrap(), - animation.value(Instant::now()), + locked, + animation.value(Instant::now()).max(0) as u16, )) } } diff --git a/core/embed/rust/src/ui/flow/swipe.rs b/core/embed/rust/src/ui/flow/swipe.rs index 7644a2219e..c47b8ce12f 100644 --- a/core/embed/rust/src/ui/flow/swipe.rs +++ b/core/embed/rust/src/ui/flow/swipe.rs @@ -170,7 +170,7 @@ impl Component for SwipeFlow { Some(Event::Swipe(SwipeEvent::End(dir))) } Some(SwipeDetectMsg::Move(dir, progress)) => { - Some(Event::Swipe(SwipeEvent::Move(dir, progress))) + Some(Event::Swipe(SwipeEvent::Move(dir, progress as i16))) } _ => Some(event), } diff --git a/core/embed/rust/src/ui/model_mercury/component/swipe_up_screen.rs b/core/embed/rust/src/ui/model_mercury/component/swipe_up_screen.rs index 43e70e393f..33c9ebfa44 100644 --- a/core/embed/rust/src/ui/model_mercury/component/swipe_up_screen.rs +++ b/core/embed/rust/src/ui/model_mercury/component/swipe_up_screen.rs @@ -49,7 +49,7 @@ impl Component for SwipeUpScreen { return Some(SwipeUpScreenMsg::Swiped); } Some(SwipeDetectMsg::Move(dir, progress)) => { - Event::Swipe(SwipeEvent::Move(dir, progress)) + Event::Swipe(SwipeEvent::Move(dir, progress as i16)) } _ => event, };