mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-24 05:12:02 +00:00
feat(eckhart): Update vertical menu components
This commit is contained in:
parent
c4982c8d0e
commit
7b02935d90
@ -6,17 +6,19 @@ mod header;
|
|||||||
mod hint;
|
mod hint;
|
||||||
mod result;
|
mod result;
|
||||||
mod text_screen;
|
mod text_screen;
|
||||||
mod vertical_menu_page;
|
mod vertical_menu;
|
||||||
|
mod vertical_menu_screen;
|
||||||
mod welcome_screen;
|
mod welcome_screen;
|
||||||
|
|
||||||
pub use action_bar::ActionBar;
|
pub use action_bar::ActionBar;
|
||||||
pub use button::{Button, ButtonMsg, ButtonStyle, ButtonStyleSheet, IconText};
|
pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, IconText};
|
||||||
pub use error::ErrorScreen;
|
pub use error::ErrorScreen;
|
||||||
pub use header::{Header, HeaderMsg};
|
pub use header::{Header, HeaderMsg};
|
||||||
pub use hint::Hint;
|
pub use hint::Hint;
|
||||||
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
||||||
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
|
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
|
||||||
pub use vertical_menu_page::VerticalMenuPage;
|
pub use vertical_menu::{VerticalMenu, VerticalMenuMsg, MENU_MAX_ITEMS};
|
||||||
|
pub use vertical_menu_screen::{VerticalMenuScreen, VerticalMenuScreenMsg};
|
||||||
pub use welcome_screen::WelcomeScreen;
|
pub use welcome_screen::WelcomeScreen;
|
||||||
|
|
||||||
use super::{constant, theme};
|
use super::{constant, theme};
|
||||||
|
198
core/embed/rust/src/ui/layout_eckhart/component/vertical_menu.rs
Normal file
198
core/embed/rust/src/ui/layout_eckhart/component/vertical_menu.rs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
component::{Component, Event, EventCtx},
|
||||||
|
geometry::{Insets, Offset, Rect},
|
||||||
|
layout_eckhart::{
|
||||||
|
component::{Button, ButtonContent, ButtonMsg},
|
||||||
|
theme,
|
||||||
|
},
|
||||||
|
shape::{Bar, Renderer},
|
||||||
|
};
|
||||||
|
|
||||||
|
use heapless::Vec;
|
||||||
|
|
||||||
|
/// Number of buttons.
|
||||||
|
/// Presently, VerticalMenu holds only fixed number of buttons.
|
||||||
|
pub const MENU_MAX_ITEMS: usize = 5;
|
||||||
|
|
||||||
|
type VerticalMenuButtons = Vec<Button, MENU_MAX_ITEMS>;
|
||||||
|
|
||||||
|
pub struct VerticalMenu {
|
||||||
|
/// Bounds the sliding window of the menu.
|
||||||
|
bounds: Rect,
|
||||||
|
/// FUll bounds of the menu, including off-screen items.
|
||||||
|
virtual_bounds: Rect,
|
||||||
|
/// Menu items.
|
||||||
|
buttons: VerticalMenuButtons,
|
||||||
|
/// Whether to show separators between buttons.
|
||||||
|
separators: bool,
|
||||||
|
/// Vertical offset of the current view.
|
||||||
|
offset_y: i16,
|
||||||
|
/// Maximum vertical offset.
|
||||||
|
max_offset: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum VerticalMenuMsg {
|
||||||
|
Selected(usize),
|
||||||
|
/// Left header button clicked
|
||||||
|
Back,
|
||||||
|
/// Right header button clicked
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerticalMenu {
|
||||||
|
const SIDE_INSET: i16 = 24;
|
||||||
|
const BUTTON_PADDING: i16 = 28;
|
||||||
|
|
||||||
|
fn new(buttons: VerticalMenuButtons) -> Self {
|
||||||
|
Self {
|
||||||
|
virtual_bounds: Rect::zero(),
|
||||||
|
bounds: Rect::zero(),
|
||||||
|
buttons,
|
||||||
|
separators: false,
|
||||||
|
offset_y: 0,
|
||||||
|
max_offset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::new(VerticalMenuButtons::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_separators(mut self) -> Self {
|
||||||
|
self.separators = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item(mut self, button: Button) -> Self {
|
||||||
|
unwrap!(self.buttons.push(button.styled(theme::menu_item_title())));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item_yellow(mut self, button: Button) -> Self {
|
||||||
|
unwrap!(self
|
||||||
|
.buttons
|
||||||
|
.push(button.styled(theme::menu_item_title_yellow())));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item_red(mut self, button: Button) -> Self {
|
||||||
|
unwrap!(self
|
||||||
|
.buttons
|
||||||
|
.push(button.styled(theme::menu_item_title_red())));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn area(&self) -> Rect {
|
||||||
|
self.bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll the menu to the desired offset.
|
||||||
|
pub fn set_offset(&mut self, offset_y: i16) {
|
||||||
|
self.offset_y = offset_y.max(0).min(self.max_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Chcek if the menu is on the bottom.
|
||||||
|
pub fn is_max_offset(&self) -> bool {
|
||||||
|
self.offset_y == self.max_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current sliding window offset.
|
||||||
|
pub fn get_offset(&self) -> i16 {
|
||||||
|
self.offset_y
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update menu buttons based on the current offset.
|
||||||
|
pub fn update_menu(&mut self, ctx: &mut EventCtx) {
|
||||||
|
for button in self.buttons.iter_mut() {
|
||||||
|
let in_bounds = button
|
||||||
|
.area()
|
||||||
|
.translate(Offset::y(-self.offset_y))
|
||||||
|
.union(self.bounds)
|
||||||
|
== self.bounds;
|
||||||
|
button.enable_if(ctx, in_bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for VerticalMenu {
|
||||||
|
type Msg = VerticalMenuMsg;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
// Crop the menu area
|
||||||
|
self.bounds = bounds.inset(Insets::sides(Self::SIDE_INSET));
|
||||||
|
|
||||||
|
let button_width = self.bounds.width();
|
||||||
|
let mut top_left = self.bounds.top_left();
|
||||||
|
|
||||||
|
for button in self.buttons.iter_mut() {
|
||||||
|
let button_height = button.content_height() + 2 * Self::BUTTON_PADDING;
|
||||||
|
|
||||||
|
// Calculate button bounds (might overflow the menu bounds)
|
||||||
|
let button_bounds =
|
||||||
|
Rect::from_top_left_and_size(top_left, Offset::new(button_width, button_height));
|
||||||
|
button.place(button_bounds);
|
||||||
|
|
||||||
|
top_left = top_left + Offset::y(button_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate virtual bounds of all buttons combined
|
||||||
|
let height = top_left.y - self.bounds.top_left().y;
|
||||||
|
self.virtual_bounds = Rect::from_top_left_and_size(
|
||||||
|
self.bounds.top_left(),
|
||||||
|
Offset::new(self.bounds.width(), height),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate maximum offset for scrolling
|
||||||
|
self.max_offset = (self.virtual_bounds.height() - self.bounds.height()).max(0);
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
for (i, button) in self.buttons.iter_mut().enumerate() {
|
||||||
|
if let Some(ButtonMsg::Clicked) = button.event(ctx, event) {
|
||||||
|
return Some(VerticalMenuMsg::Selected(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||||
|
// Clip and translate the sliding window based on the scroll offset
|
||||||
|
target.in_clip(self.bounds, &|target| {
|
||||||
|
target.with_origin(Offset::y(-self.offset_y), &|target| {
|
||||||
|
// Render menu button
|
||||||
|
for button in (&self.buttons).into_iter() {
|
||||||
|
button.render(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render separators between buttons
|
||||||
|
if self.separators {
|
||||||
|
for i in 1..self.buttons.len() {
|
||||||
|
let button = self.buttons.get(i).unwrap();
|
||||||
|
|
||||||
|
// Render a line above the button
|
||||||
|
let separator = Rect::from_top_left_and_size(
|
||||||
|
button.area().top_left(),
|
||||||
|
Offset::new(button.area().width(), 1),
|
||||||
|
);
|
||||||
|
Bar::new(separator)
|
||||||
|
.with_fg(theme::GREY_EXTRA_DARK)
|
||||||
|
.render(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl crate::trace::Trace for VerticalMenu {
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.component("VerticalMenu");
|
||||||
|
t.in_list("buttons", &|button_list| {
|
||||||
|
for button in &self.buttons {
|
||||||
|
button_list.child(button);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,133 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
strutil::TString,
|
|
||||||
ui::{
|
|
||||||
component::{Component, Event, EventCtx},
|
|
||||||
display::Icon,
|
|
||||||
geometry::Rect,
|
|
||||||
layout_eckhart::{component::button::IconText, theme},
|
|
||||||
shape::Renderer,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use heapless::Vec;
|
|
||||||
|
|
||||||
use super::{button::ButtonMsg, constant, Button, Header, HeaderMsg};
|
|
||||||
|
|
||||||
/// Number of buttons.
|
|
||||||
/// Presently, VerticalMenu holds only fixed number of buttons.
|
|
||||||
const MENU_MAX_ITEMS: usize = 4;
|
|
||||||
|
|
||||||
type VerticalMenuButtons = Vec<Button, MENU_MAX_ITEMS>;
|
|
||||||
|
|
||||||
/// TODO: this is just a mockup for now
|
|
||||||
pub struct VerticalMenuPage {
|
|
||||||
header: Header,
|
|
||||||
buttons: VerticalMenuButtons,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum VerticalMenuMsg {
|
|
||||||
Selected(usize),
|
|
||||||
/// Left header button clicked
|
|
||||||
Back,
|
|
||||||
/// Right header button clicked
|
|
||||||
Close,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerticalMenuPage {
|
|
||||||
fn new(buttons: VerticalMenuButtons) -> Self {
|
|
||||||
Self {
|
|
||||||
header: Header::new(TString::empty()),
|
|
||||||
buttons,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self::new(VerticalMenuButtons::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_header(mut self, header: Header) -> Self {
|
|
||||||
self.header = header;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn item(mut self, icon: Icon, text: TString<'static>) -> Self {
|
|
||||||
unwrap!(self.buttons.push(
|
|
||||||
Button::with_icon_and_text(IconText::new(text, icon)).styled(theme::button_default())
|
|
||||||
));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn danger(mut self, icon: Icon, text: TString<'static>) -> Self {
|
|
||||||
unwrap!(
|
|
||||||
(self.buttons.push(
|
|
||||||
Button::with_icon_and_text(IconText::new(text, icon))
|
|
||||||
.styled(theme::button_warning_high())
|
|
||||||
)),
|
|
||||||
"unwrap failed"
|
|
||||||
);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for VerticalMenuPage {
|
|
||||||
type Msg = VerticalMenuMsg;
|
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
|
||||||
// assert full screen
|
|
||||||
debug_assert_eq!(bounds.height(), constant::HEIGHT);
|
|
||||||
debug_assert_eq!(bounds.width(), constant::WIDTH);
|
|
||||||
|
|
||||||
const MENU_BUTTON_HEIGHT: i16 = 64; // TODO: variable height buttons
|
|
||||||
/// Fixed height of a separator.
|
|
||||||
const MENU_SEP_HEIGHT: i16 = 2;
|
|
||||||
let n_seps = self.buttons.len() - 1;
|
|
||||||
let (header_area, mut rest) = bounds.split_top(Header::HEADER_HEIGHT);
|
|
||||||
for (i, button) in self.buttons.iter_mut().enumerate() {
|
|
||||||
let (area_button, new_remaining) = rest.split_top(MENU_BUTTON_HEIGHT);
|
|
||||||
button.place(area_button);
|
|
||||||
rest = new_remaining;
|
|
||||||
if i < n_seps {
|
|
||||||
let (_area_sep, new_remaining) = rest.split_top(MENU_SEP_HEIGHT);
|
|
||||||
rest = new_remaining;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.header.place(header_area);
|
|
||||||
|
|
||||||
bounds
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
|
||||||
if let Some(msg) = self.header.event(ctx, event) {
|
|
||||||
match msg {
|
|
||||||
HeaderMsg::Cancelled => return Some(VerticalMenuMsg::Close),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i, button) in self.buttons.iter_mut().enumerate() {
|
|
||||||
if let Some(ButtonMsg::Clicked) = button.event(ctx, event) {
|
|
||||||
return Some(VerticalMenuMsg::Selected(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
|
||||||
for (i, button) in (&self.buttons).into_iter().enumerate() {
|
|
||||||
button.render(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
|
||||||
impl crate::trace::Trace for VerticalMenuPage {
|
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
|
||||||
t.component("VerticalMenuPage");
|
|
||||||
t.in_list("buttons", &|button_list| {
|
|
||||||
for button in &self.buttons {
|
|
||||||
button_list.child(button);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,193 @@
|
|||||||
|
use crate::{
|
||||||
|
strutil::TString,
|
||||||
|
ui::{
|
||||||
|
component::{
|
||||||
|
base::AttachType,
|
||||||
|
swipe_detect::{SwipeConfig, SwipeSettings},
|
||||||
|
Component, Event, EventCtx, SwipeDetect,
|
||||||
|
},
|
||||||
|
event::{SwipeEvent, TouchEvent},
|
||||||
|
geometry::{Alignment2D, Direction, Offset, Rect},
|
||||||
|
layout_eckhart::{
|
||||||
|
component::{constant::screen, Header, HeaderMsg, VerticalMenu, VerticalMenuMsg},
|
||||||
|
theme,
|
||||||
|
},
|
||||||
|
shape::{Renderer, ToifImage},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct VerticalMenuScreen {
|
||||||
|
header: Header,
|
||||||
|
/// Scrollable vertical menu
|
||||||
|
menu: VerticalMenu,
|
||||||
|
/// Base position of the menu sliding window to scroll around
|
||||||
|
offset_base: i16,
|
||||||
|
/// Swipe detector
|
||||||
|
swipe: SwipeDetect,
|
||||||
|
/// Swipe configuration
|
||||||
|
swipe_config: SwipeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum VerticalMenuScreenMsg {
|
||||||
|
Selected(usize),
|
||||||
|
/// Left header button clicked
|
||||||
|
Back,
|
||||||
|
/// Right header button clicked
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerticalMenuScreen {
|
||||||
|
pub fn new(menu: VerticalMenu) -> Self {
|
||||||
|
Self {
|
||||||
|
header: Header::new(TString::empty()),
|
||||||
|
menu,
|
||||||
|
offset_base: 0,
|
||||||
|
swipe: SwipeDetect::new(),
|
||||||
|
swipe_config: SwipeConfig::new()
|
||||||
|
.with_swipe(Direction::Up, SwipeSettings::default())
|
||||||
|
.with_swipe(Direction::Down, SwipeSettings::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_header(mut self, header: Header) -> Self {
|
||||||
|
self.header = header;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift position of touch events in the menu area by an offset of the current
|
||||||
|
// sliding window position
|
||||||
|
fn shift_touch_event(&self, event: Event) -> Option<Event> {
|
||||||
|
match event {
|
||||||
|
Event::Touch(touch_event) => {
|
||||||
|
let shifted_event = match touch_event {
|
||||||
|
TouchEvent::TouchStart(point) if self.menu.area().contains(point) => Some(
|
||||||
|
TouchEvent::TouchStart(point.ofs(Offset::y(self.menu.get_offset())).into()),
|
||||||
|
),
|
||||||
|
TouchEvent::TouchMove(point) if self.menu.area().contains(point) => Some(
|
||||||
|
TouchEvent::TouchMove(point.ofs(Offset::y(self.menu.get_offset())).into()),
|
||||||
|
),
|
||||||
|
TouchEvent::TouchEnd(point) if self.menu.area().contains(point) => Some(
|
||||||
|
TouchEvent::TouchEnd(point.ofs(Offset::y(self.menu.get_offset())).into()),
|
||||||
|
),
|
||||||
|
_ => None, // Ignore touch events outside the bounds
|
||||||
|
};
|
||||||
|
shifted_event.map(Event::Touch)
|
||||||
|
}
|
||||||
|
_ => None, // Ignore other events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update menu buttons based on the current offset.
|
||||||
|
pub fn update_menu(&mut self, ctx: &mut EventCtx) {
|
||||||
|
self.menu.update_menu(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for VerticalMenuScreen {
|
||||||
|
type Msg = VerticalMenuScreenMsg;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
// assert full screen
|
||||||
|
debug_assert_eq!(bounds.height(), screen().height());
|
||||||
|
debug_assert_eq!(bounds.width(), screen().width());
|
||||||
|
|
||||||
|
let (header_area, menu_area) = bounds.split_top(Header::HEADER_HEIGHT);
|
||||||
|
|
||||||
|
self.menu.place(menu_area);
|
||||||
|
self.header.place(header_area);
|
||||||
|
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
// Update the menu when the screen is attached
|
||||||
|
if let Event::Attach(AttachType::Initial) = event {
|
||||||
|
self.update_menu(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.swipe.event(ctx, event, self.swipe_config) {
|
||||||
|
Some(SwipeEvent::Start(_)) => {
|
||||||
|
// Lock the base position to scroll around
|
||||||
|
self.offset_base = self.menu.get_offset();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(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 / 10;
|
||||||
|
// 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),
|
||||||
|
HeaderMsg::Back => return Some(VerticalMenuScreenMsg::Back),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift touch events in the menu area by the current sliding window position
|
||||||
|
if let Some(shifted) = self.shift_touch_event(event) {
|
||||||
|
if let Some(msg) = self.menu.event(ctx, shifted) {
|
||||||
|
match msg {
|
||||||
|
VerticalMenuMsg::Selected(i) => {
|
||||||
|
return Some(VerticalMenuScreenMsg::Selected(i))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle shifted touch events in the menu
|
||||||
|
if let Some(msg) = self.menu.event(ctx, event) {
|
||||||
|
match msg {
|
||||||
|
VerticalMenuMsg::Selected(i) => return Some(VerticalMenuScreenMsg::Selected(i)),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 can be scrolled down
|
||||||
|
if !self.menu.is_max_offset() {
|
||||||
|
ToifImage::new(
|
||||||
|
self.menu.area().bottom_center(),
|
||||||
|
theme::ICON_CHEVRON_DOWN_MINI.toif,
|
||||||
|
)
|
||||||
|
.with_align(Alignment2D::BOTTOM_CENTER)
|
||||||
|
.with_fg(theme::GREY_LIGHT)
|
||||||
|
.render(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl crate::trace::Trace for VerticalMenuScreen {
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.component("VerticalMenuScreen");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user