1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-05-03 15:39:03 +00:00

chore(eckhart): enable vertical menu swipe without animations

This commit is contained in:
Lukas Bielesch 2025-04-15 11:58:19 +02:00 committed by Lukáš Bielesch
parent 23c4c26a35
commit 8906d8c4a1
2 changed files with 156 additions and 66 deletions

View File

@ -1,8 +1,9 @@
use crate::ui::{
component::{Component, Event, EventCtx},
event::TouchEvent,
geometry::{Insets, Offset, Rect},
geometry::{Direction, Insets, Offset, Rect},
shape::{Bar, Renderer},
util::animation_disabled,
};
use super::{
@ -90,7 +91,7 @@ impl VerticalMenu {
/// Enable only buttons that are fully visible in the menu area.
/// Meaningful only if the menu is scrollable.
/// If the menu fits its area, all buttons are enabled.
pub fn update_menu(&mut self, ctx: &mut EventCtx) {
pub fn update_button_states(&mut self, ctx: &mut EventCtx) {
for button in self.buttons.iter_mut() {
let in_bounds = button
.area()
@ -101,7 +102,57 @@ impl VerticalMenu {
}
}
/// Scroll the menu by one item in given direction.
/// Relevant only for testing purposes when the animations are disabled.
#[cfg(feature = "ui_debug")]
pub fn scroll_item(&mut self, dir: Direction) {
// Make sure the animations are disabled
debug_assert!(animation_disabled());
// Only vertical swipes are allowed
debug_assert!(dir == Direction::Up || dir == Direction::Down);
// For single button, the menu is not scrollable
if self.buttons.len() < 2 {
return;
}
// The offset could reach only discrete values of cumsum of button heights
let current = self.offset_y;
let mut cumsum = 0;
for button in &self.buttons[..self.buttons.len() - 1] {
let new_cumsum = cumsum + button.area().height();
match dir {
Direction::Up if new_cumsum > current => {
self.set_offset(new_cumsum);
break;
}
Direction::Down if new_cumsum >= current => {
self.set_offset(cumsum);
break;
}
_ => {
cumsum = new_cumsum;
}
}
}
}
fn set_max_offset(&mut self) {
// Relevant only for testing when the animations are disabled
// The menu is scrollable until the last button is visible
#[cfg(feature = "ui_debug")]
if animation_disabled() {
self.offset_y_max = self.total_height
- self
.buttons
.last()
.unwrap_or(&Button::empty())
.area()
.height();
return;
}
// Calculate the overflow of the menu area
let menu_overflow = (self.total_height - self.bounds.height()).max(0);

View File

@ -9,7 +9,7 @@ use crate::{
flow::Swipable,
geometry::{Alignment2D, Direction, Rect},
shape::{Renderer, ToifImage},
util::Pager,
util::{animation_disabled, Pager},
},
};
@ -21,10 +21,8 @@ pub struct VerticalMenuScreen {
menu: VerticalMenu,
/// Base position of the menu sliding window to scroll around
offset_base: i16,
/// Used to enable swipe detection only when the menu does not fit its area
swipe_enabled: bool,
/// Swipe detector
swipe: SwipeDetect,
swipe: Option<SwipeDetect>,
/// Swipe configuration
swipe_config: SwipeConfig,
}
@ -44,8 +42,7 @@ impl VerticalMenuScreen {
header: Header::new(TString::empty()),
menu,
offset_base: 0,
swipe_enabled: false,
swipe: SwipeDetect::new(),
swipe: None,
swipe_config: SwipeConfig::new()
.with_swipe(Direction::Up, SwipeSettings::default())
.with_swipe(Direction::Down, SwipeSettings::default()),
@ -59,20 +56,108 @@ impl VerticalMenuScreen {
/// Update swipe detection and buttons state based on menu size
pub fn initialize_screen(&mut self, ctx: &mut EventCtx) {
if !self.menu.fits_area() {
// Enable swipe
self.swipe_enabled = true;
self.swipe_config = SwipeConfig::new()
.with_swipe(Direction::Up, SwipeSettings::default())
.with_swipe(Direction::Down, SwipeSettings::default());
#[cfg(feature = "ui_debug")]
if animation_disabled() {
self.swipe = Some(SwipeDetect::new());
ctx.enable_swipe();
// Set default position for the sliding window
self.menu.set_offset(0);
// Update the menu buttons state
self.menu.update_menu(ctx);
self.menu.update_button_states(ctx);
return;
}
// Switch swiping on/off based on the menu fit
self.swipe = if !self.menu.fits_area() {
ctx.enable_swipe();
Some(SwipeDetect::new())
} else {
// Disable swipe
self.swipe_enabled = false;
ctx.disable_swipe();
None
};
// Set default position for the sliding window
self.menu.set_offset(0);
// Update button states
self.menu.update_button_states(ctx);
}
fn handle_swipe_event(&mut self, ctx: &mut EventCtx, event: Event) {
// Relevant only for testing when the animations are disabled
// The menu is scrollable until the last button is visible
#[cfg(feature = "ui_debug")]
if animation_disabled() {
// Handle swipes from the standalone swipe detector or ones coming from
// the flow. These two are mutually exclusive and should not be triggered at the
// same time.
let direction = self
.swipe
.as_mut()
.and_then(|swipe| swipe.event(ctx, event, self.swipe_config))
.and_then(|e| match e {
SwipeEvent::End(dir @ (Direction::Up | Direction::Down)) => Some(dir),
_ => None,
})
.or(match event {
Event::Swipe(SwipeEvent::End(dir @ (Direction::Up | Direction::Down))) => {
Some(dir)
}
_ => None,
});
if let Some(dir) = direction {
self.menu.scroll_item(dir);
self.menu.update_button_states(ctx);
ctx.request_paint();
}
return;
}
if let Some(swipe) = &mut self.swipe {
// Handle swipe events from the standalone swipe detector or ones coming from
// the flow. These two are mutually exclusive and should not be triggered at the
// same time.
let swipe_event = swipe.event(ctx, event, self.swipe_config).or(match event {
Event::Swipe(e) => Some(e),
_ => None,
});
match swipe_event {
Some(SwipeEvent::Start(_) | SwipeEvent::End(_)) => {
// Lock the base position to scroll around
self.offset_base = self.menu.get_offset();
}
Some(SwipeEvent::Move(dir @ (Direction::Up | Direction::Down), delta)) => {
// Reduce swipe sensitivity
let delta = delta / Self::TOUCH_SENSITIVITY_DIVIDER;
let offset = match dir {
Direction::Up => self.offset_base + delta,
Direction::Down => self.offset_base - delta,
_ => unreachable!(), // Already matched Up or Down
};
self.menu.set_offset(offset);
self.menu.update_button_states(ctx);
}
_ => {}
}
}
}
fn render_overflow_arrow<'s>(&'s self, target: &mut impl Renderer<'s>) {
// Do not render the arrow if animations are disabled
#[cfg(feature = "ui_debug")]
if animation_disabled() {
return;
}
// Render the down arrow if the menu overflows and can be scrolled further down
if self.swipe.is_some() && !self.menu.is_max_offset() {
ToifImage::new(SCREEN.bottom_center(), theme::ICON_CHEVRON_DOWN_MINI.toif)
.with_align(Alignment2D::BOTTOM_CENTER)
.with_fg(theme::GREY_LIGHT)
.render(target);
}
}
}
@ -100,46 +185,6 @@ impl Component for VerticalMenuScreen {
self.initialize_screen(ctx);
}
// Handle swipe events if swipe is enabled (menu does not fit)
if self.swipe_enabled {
// Handle swipe events from the standalone swipe detector or ones coming from
// the flow. These two are mutually exclusive and should not be triggered at the
// same time.
let swipe_event = self
.swipe
.event(ctx, event, self.swipe_config)
.or(match event {
Event::Swipe(e) => Some(e),
_ => None,
});
match swipe_event {
Some(SwipeEvent::Start(_) | SwipeEvent::End(_)) => {
// Lock the base position to scroll around
self.offset_base = self.menu.get_offset();
}
Some(SwipeEvent::Move(dir, delta)) => {
// Decrease the sensitivity of the swipe
let delta = delta / Self::TOUCH_SENSITIVITY_DIVIDER;
// Scroll the menu based on the swipe direction
match dir {
Direction::Up => {
self.menu.set_offset(self.offset_base + delta);
self.menu.update_menu(ctx);
return None;
}
Direction::Down => {
self.menu.set_offset(self.offset_base - delta);
self.menu.update_menu(ctx);
return None;
}
_ => {}
}
}
_ => {}
}
}
if let Some(msg) = self.header.event(ctx, event) {
match msg {
HeaderMsg::Cancelled => return Some(VerticalMenuScreenMsg::Close),
@ -152,20 +197,14 @@ impl Component for VerticalMenuScreen {
return Some(VerticalMenuScreenMsg::Selected(i));
}
self.handle_swipe_event(ctx, event);
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.header.render(target);
self.menu.render(target);
// Render the down arrow if the menu overflows and can be scrolled further down
if !self.menu.fits_area() && !self.menu.is_max_offset() {
ToifImage::new(SCREEN.bottom_center(), theme::ICON_CHEVRON_DOWN_MINI.toif)
.with_align(Alignment2D::BOTTOM_CENTER)
.with_fg(theme::GREY_LIGHT)
.render(target);
}
self.render_overflow_arrow(target);
}
}