Skip to content

Commit 0d65b4a

Browse files
committed
fix(eckhart): improve haptics in scrollable menu
- if the menu is scrollable (i.e. buttons don't fit on the page) the haptics is postponed to the actual click (TouchEnd) rather than placing the finger there (TouchStart)
1 parent 2a24b16 commit 0d65b4a

4 files changed

Lines changed: 41 additions & 17 deletions

File tree

core/embed/rust/src/ui/layout_eckhart/component/button.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ enum RadiusOrGradient {
3232
None,
3333
}
3434

35+
#[derive(Clone, Copy)]
36+
pub enum HapticMode {
37+
/// Vibrate on TouchStart (default)
38+
OnPress,
39+
/// Vibrate only on confirmed click
40+
OnClick,
41+
/// No haptic feedback
42+
Off,
43+
}
44+
3545
pub struct Button {
3646
area: Rect,
3747
touch_expand: Insets,
@@ -44,7 +54,7 @@ pub struct Button {
4454
long_press: ShortDuration, // long press requires non-zero duration
4555
long_press_danger: bool,
4656
long_timer: Timer,
47-
haptic: bool,
57+
haptic: HapticMode,
4858
subtext_marquee: Option<Marquee>,
4959
#[cfg(feature = "ui_debug")]
5060
skip_test_visit: bool, // used by debuglink
@@ -87,7 +97,7 @@ impl Button {
8797
long_press: ShortDuration::ZERO,
8898
long_press_danger: false,
8999
long_timer: Timer::new(),
90-
haptic: true,
100+
haptic: HapticMode::OnPress,
91101
subtext_marquee,
92102
#[cfg(feature = "ui_debug")]
93103
skip_test_visit: false,
@@ -264,11 +274,6 @@ impl Button {
264274
self
265275
}
266276

267-
pub fn without_haptics(mut self) -> Self {
268-
self.haptic = false;
269-
self
270-
}
271-
272277
pub fn with_gradient(mut self, gradient: Gradient) -> Self {
273278
self.radius_or_gradient = RadiusOrGradient::Gradient(gradient);
274279
self
@@ -454,6 +459,10 @@ impl Button {
454459
}
455460
}
456461

462+
pub fn set_haptic_mode(&mut self, mode: HapticMode) {
463+
self.haptic = mode;
464+
}
465+
457466
pub fn render_background<'s>(
458467
&self,
459468
target: &mut impl Renderer<'s>,
@@ -714,7 +723,7 @@ impl Component for Button {
714723
// Touch started in our area, transform to `Pressed` state.
715724
if touch_area.contains(pos) {
716725
#[cfg(feature = "haptic")]
717-
if self.haptic {
726+
if matches!(self.haptic, HapticMode::OnPress) {
718727
play(HapticEffect::ButtonPress);
719728
}
720729
self.set(ctx, State::Pressed);
@@ -745,6 +754,10 @@ impl Component for Button {
745754
}
746755
State::Pressed if touch_area.contains(pos) => {
747756
// Touch finished in our area, we got clicked.
757+
#[cfg(feature = "haptic")]
758+
if matches!(self.haptic, HapticMode::OnClick) {
759+
play(HapticEffect::ButtonPress);
760+
}
748761
self.set(ctx, State::Initial);
749762
return Some(ButtonMsg::Clicked);
750763
}
@@ -784,7 +797,7 @@ impl Component for Button {
784797
Event::Timer(_) if self.long_timer.expire(event) => {
785798
if matches!(self.state, State::Pressed) {
786799
#[cfg(feature = "haptic")]
787-
if self.haptic {
800+
if !matches!(self.haptic, HapticMode::Off) {
788801
play(HapticEffect::ButtonPress);
789802
}
790803
self.set(ctx, State::Initial);

core/embed/rust/src/ui/layout_eckhart/component/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ mod fuel_gauge;
44
mod update_screen;
55
mod welcome_screen;
66

7-
pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, IconText};
7+
pub use button::{
8+
Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, HapticMode, IconText,
9+
};
810
pub use error::ErrorScreen;
911
pub use fuel_gauge::FuelGauge;
1012
pub use update_screen::UpdateScreen;

core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::{
1212
};
1313

1414
use super::{
15-
super::component::{Button, ButtonMsg},
15+
super::component::{Button, ButtonMsg, HapticMode},
1616
theme,
1717
};
1818
use heapless::Vec;
@@ -139,6 +139,13 @@ impl<T: MenuItems> VerticalMenu<T> {
139139
self
140140
}
141141

142+
/// Set haptic mode for all buttons in the menu.
143+
pub fn set_haptic_mode(&mut self, mode: HapticMode) {
144+
for button in self.buttons.iter_mut() {
145+
button.set_haptic_mode(mode);
146+
}
147+
}
148+
142149
pub fn item(&mut self, button: Button) -> &mut Self {
143150
self.buttons.push(button);
144151
self

core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu_screen.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use crate::{
1616
};
1717

1818
use super::{
19-
constant::SCREEN, theme, Header, HeaderMsg, MenuItems, ShortMenuVec, VerticalMenu,
20-
VerticalMenuMsg,
19+
super::component::HapticMode, constant::SCREEN, theme, Header, HeaderMsg, MenuItems,
20+
ShortMenuVec, VerticalMenu, VerticalMenuMsg,
2121
};
2222

2323
pub struct VerticalMenuScreen<T> {
@@ -95,13 +95,15 @@ impl<T: MenuItems> VerticalMenuScreen<T> {
9595
}
9696

9797
// Switch swiping on/off based on the menu fit
98-
self.swipe = if !self.menu.fits_area() {
98+
if !self.menu.fits_area() {
9999
ctx.enable_swipe();
100-
Some(SwipeDetect::new())
100+
self.swipe = Some(SwipeDetect::new());
101+
// Delay haptic feedback to click for scrollable menus
102+
self.menu.set_haptic_mode(HapticMode::OnClick);
101103
} else {
102104
ctx.disable_swipe();
103-
None
104-
};
105+
self.swipe = None;
106+
}
105107

106108
// Set default position for the sliding window
107109
self.menu.set_offset(0);

0 commit comments

Comments
 (0)