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:
parent
23c4c26a35
commit
8906d8c4a1
@ -1,8 +1,9 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{Component, Event, EventCtx},
|
component::{Component, Event, EventCtx},
|
||||||
event::TouchEvent,
|
event::TouchEvent,
|
||||||
geometry::{Insets, Offset, Rect},
|
geometry::{Direction, Insets, Offset, Rect},
|
||||||
shape::{Bar, Renderer},
|
shape::{Bar, Renderer},
|
||||||
|
util::animation_disabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -90,7 +91,7 @@ impl VerticalMenu {
|
|||||||
/// Enable only buttons that are fully visible in the menu area.
|
/// Enable only buttons that are fully visible in the menu area.
|
||||||
/// Meaningful only if the menu is scrollable.
|
/// Meaningful only if the menu is scrollable.
|
||||||
/// If the menu fits its area, all buttons are enabled.
|
/// 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() {
|
for button in self.buttons.iter_mut() {
|
||||||
let in_bounds = button
|
let in_bounds = button
|
||||||
.area()
|
.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) {
|
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
|
// Calculate the overflow of the menu area
|
||||||
let menu_overflow = (self.total_height - self.bounds.height()).max(0);
|
let menu_overflow = (self.total_height - self.bounds.height()).max(0);
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ use crate::{
|
|||||||
flow::Swipable,
|
flow::Swipable,
|
||||||
geometry::{Alignment2D, Direction, Rect},
|
geometry::{Alignment2D, Direction, Rect},
|
||||||
shape::{Renderer, ToifImage},
|
shape::{Renderer, ToifImage},
|
||||||
util::Pager,
|
util::{animation_disabled, Pager},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -21,10 +21,8 @@ pub struct VerticalMenuScreen {
|
|||||||
menu: VerticalMenu,
|
menu: VerticalMenu,
|
||||||
/// Base position of the menu sliding window to scroll around
|
/// Base position of the menu sliding window to scroll around
|
||||||
offset_base: i16,
|
offset_base: i16,
|
||||||
/// Used to enable swipe detection only when the menu does not fit its area
|
|
||||||
swipe_enabled: bool,
|
|
||||||
/// Swipe detector
|
/// Swipe detector
|
||||||
swipe: SwipeDetect,
|
swipe: Option<SwipeDetect>,
|
||||||
/// Swipe configuration
|
/// Swipe configuration
|
||||||
swipe_config: SwipeConfig,
|
swipe_config: SwipeConfig,
|
||||||
}
|
}
|
||||||
@ -44,8 +42,7 @@ impl VerticalMenuScreen {
|
|||||||
header: Header::new(TString::empty()),
|
header: Header::new(TString::empty()),
|
||||||
menu,
|
menu,
|
||||||
offset_base: 0,
|
offset_base: 0,
|
||||||
swipe_enabled: false,
|
swipe: None,
|
||||||
swipe: SwipeDetect::new(),
|
|
||||||
swipe_config: SwipeConfig::new()
|
swipe_config: SwipeConfig::new()
|
||||||
.with_swipe(Direction::Up, SwipeSettings::default())
|
.with_swipe(Direction::Up, SwipeSettings::default())
|
||||||
.with_swipe(Direction::Down, SwipeSettings::default()),
|
.with_swipe(Direction::Down, SwipeSettings::default()),
|
||||||
@ -59,20 +56,108 @@ impl VerticalMenuScreen {
|
|||||||
|
|
||||||
/// Update swipe detection and buttons state based on menu size
|
/// Update swipe detection and buttons state based on menu size
|
||||||
pub fn initialize_screen(&mut self, ctx: &mut EventCtx) {
|
pub fn initialize_screen(&mut self, ctx: &mut EventCtx) {
|
||||||
if !self.menu.fits_area() {
|
#[cfg(feature = "ui_debug")]
|
||||||
// Enable swipe
|
if animation_disabled() {
|
||||||
self.swipe_enabled = true;
|
self.swipe = Some(SwipeDetect::new());
|
||||||
self.swipe_config = SwipeConfig::new()
|
|
||||||
.with_swipe(Direction::Up, SwipeSettings::default())
|
|
||||||
.with_swipe(Direction::Down, SwipeSettings::default());
|
|
||||||
ctx.enable_swipe();
|
ctx.enable_swipe();
|
||||||
|
// Set default position for the sliding window
|
||||||
|
self.menu.set_offset(0);
|
||||||
// Update the menu buttons state
|
// 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 {
|
} else {
|
||||||
// Disable swipe
|
|
||||||
self.swipe_enabled = false;
|
|
||||||
ctx.disable_swipe();
|
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);
|
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) {
|
if let Some(msg) = self.header.event(ctx, event) {
|
||||||
match msg {
|
match msg {
|
||||||
HeaderMsg::Cancelled => return Some(VerticalMenuScreenMsg::Close),
|
HeaderMsg::Cancelled => return Some(VerticalMenuScreenMsg::Close),
|
||||||
@ -152,20 +197,14 @@ impl Component for VerticalMenuScreen {
|
|||||||
return Some(VerticalMenuScreenMsg::Selected(i));
|
return Some(VerticalMenuScreenMsg::Selected(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.handle_swipe_event(ctx, event);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||||
self.header.render(target);
|
self.header.render(target);
|
||||||
self.menu.render(target);
|
self.menu.render(target);
|
||||||
|
self.render_overflow_arrow(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user