From 670cbd7a1deeed4199f50f09cf2823d5f555cd10 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Fri, 10 Mar 2023 16:42:45 +0100 Subject: [PATCH] fix(core/ui): style update: paging [no changelog] --- .../rust/src/ui/model_tt/component/page.rs | 217 ++++++++++++------ core/embed/rust/src/ui/model_tt/layout.rs | 19 +- core/embed/rust/src/ui/model_tt/theme.rs | 10 +- 3 files changed, 164 insertions(+), 82 deletions(-) diff --git a/core/embed/rust/src/ui/model_tt/component/page.rs b/core/embed/rust/src/ui/model_tt/component/page.rs index b1cf772811..0fd7030844 100644 --- a/core/embed/rust/src/ui/model_tt/component/page.rs +++ b/core/embed/rust/src/ui/model_tt/component/page.rs @@ -2,11 +2,11 @@ use crate::ui::{ component::{ base::ComponentExt, paginated::{AuxPageMsg, PageMsg}, - Component, Event, EventCtx, FixedHeightBar, Label, Pad, Paginate, + Component, Event, EventCtx, FixedHeightBar, Pad, Paginate, }, display::{self, toif::Icon, Color}, - geometry::{Insets, Rect}, - model_tt::component::{Button, ButtonMsg}, + geometry::{Grid, Insets, Rect}, + model_tt::component::{Button, ButtonContent, ButtonMsg}, }; use super::{ @@ -14,14 +14,53 @@ use super::{ theme, CancelConfirmMsg, Loader, ScrollBar, Swipe, SwipeDirection, }; -pub struct SwipePage { +/// Describes behavior of left button. +enum ButtonPrevCancels { + /// Button never causes `PageMsg::Aux(AuxPageMsg::GoBack)` to be emitted. + Never, + + /// Button cancels the layout if pressed on the first page. Otherwise it + /// goes to previous page. + FirstPage, + + /// Button cancels the layout on any page, except the last where controls + /// are displayed. + AnyPage, +} + +impl ButtonPrevCancels { + fn should_cancel(&self, is_first_page: bool) -> bool { + match self { + ButtonPrevCancels::Never => false, + ButtonPrevCancels::FirstPage => is_first_page, + ButtonPrevCancels::AnyPage => true, + } + } + + fn icon(&self, is_first_page: bool) -> Icon { + let data = match self { + ButtonPrevCancels::Never => theme::ICON_UP, + ButtonPrevCancels::FirstPage if is_first_page => theme::ICON_CANCEL, + ButtonPrevCancels::FirstPage => theme::ICON_UP, + ButtonPrevCancels::AnyPage => theme::ICON_BACK, + }; + Icon::new(data) + } +} + +pub struct SwipePage +where + U: Component, +{ content: T, - buttons: U, + controls: U, pad: Pad, swipe: Swipe, scrollbar: ScrollBar, - hint: Label<&'static str>, - button_back: Option>, + button_prev: Button<&'static str>, + button_next: Button<&'static str>, + button_prev_cancels: ButtonPrevCancels, + is_go_back: Option bool>, swipe_left: bool, fade: Option, } @@ -32,22 +71,38 @@ where T: Component, U: Component, { - pub fn new(content: T, buttons: U, background: Color) -> Self { + pub fn new(content: T, controls: U, background: Color) -> Self { Self { content, - buttons, + controls, scrollbar: ScrollBar::vertical(), swipe: Swipe::new(), pad: Pad::with_background(background), - hint: Label::centered("SWIPE TO CONTINUE", theme::label_page_hint()), - button_back: None, + button_prev: Button::with_icon(Icon::new(theme::ICON_UP)).initially_enabled(false), + button_next: Button::with_icon(Icon::new(theme::ICON_DOWN)), + button_prev_cancels: ButtonPrevCancels::Never, + is_go_back: None, swipe_left: false, fade: None, } } pub fn with_back_button(mut self) -> Self { - self.button_back = Some(Button::with_icon(Icon::new(theme::ICON_BACK))); + self.button_prev_cancels = ButtonPrevCancels::AnyPage; + self.button_prev = Button::with_icon(Icon::new(theme::ICON_BACK)).initially_enabled(true); + self + } + + pub fn with_cancel_on_first_page(mut self) -> Self { + self.button_prev_cancels = ButtonPrevCancels::FirstPage; + self.button_prev = Button::with_icon(Icon::new(theme::ICON_CANCEL)).initially_enabled(true); + self + } + + /// If `controls` message matches the function then we will go page back + /// instead of propagating the message to parent component. + pub fn with_go_back(mut self, is_go_back: fn(&U::Msg) -> bool) -> Self { + self.is_go_back = Some(is_go_back); self } @@ -66,6 +121,25 @@ where // Adjust the swipe parameters according to the scrollbar. self.setup_swipe(); + // Enable/disable prev/next buttons. + self.button_prev.set_content( + ctx, + ButtonContent::Icon( + self.button_prev_cancels + .icon(self.scrollbar.active_page == 0), + ), + ); + self.button_prev.enable_if( + ctx, + self.scrollbar.has_previous_page() + || matches!( + self.button_prev_cancels, + ButtonPrevCancels::FirstPage | ButtonPrevCancels::AnyPage + ), + ); + self.button_next + .enable_if(ctx, self.scrollbar.has_next_page()); + // Change the page in the content, make sure it gets completely repainted and // clear the background under it. self.content.change_page(self.scrollbar.active_page); @@ -83,15 +157,10 @@ where let mut layout = PageLayout::new(bounds); self.pad.place(bounds); self.swipe.place(bounds); + self.button_prev.place(layout.button_prev); + self.button_next.place(layout.button_next); - if self.button_back.is_some() { - self.button_back.place(layout.hint_button); - self.hint.place(layout.hint_button_hint); - } else { - self.hint.place(layout.hint); - } - - let buttons_area = self.buttons.place(layout.buttons); + let buttons_area = self.controls.place(layout.controls); layout.set_buttons_height(buttons_area.height()); self.scrollbar.place(layout.scrollbar); @@ -159,14 +228,35 @@ where return Some(PageMsg::Content(msg)); } if !self.scrollbar.has_next_page() { - if let Some(msg) = self.buttons.event(ctx, event) { + if let Some(msg) = self.controls.event(ctx, event) { + // Handle the case when one of the controls buttons is configured to go back a + // page. + if let Some(f) = self.is_go_back { + if f(&msg) { + self.scrollbar.go_to_previous_page(); + self.on_page_change(ctx); + return None; + } + } return Some(PageMsg::Controls(msg)); } } else { - if let Some(ButtonMsg::Clicked) = self.button_back.event(ctx, event) { - return Some(PageMsg::Aux(AuxPageMsg::GoBack)); + if let Some(ButtonMsg::Clicked) = self.button_prev.event(ctx, event) { + if self + .button_prev_cancels + .should_cancel(self.scrollbar.active_page == 0) + { + return Some(PageMsg::Aux(AuxPageMsg::GoBack)); + } + self.scrollbar.go_to_previous_page(); + self.on_page_change(ctx); + return None; + } + if let Some(ButtonMsg::Clicked) = self.button_next.event(ctx, event) { + self.scrollbar.go_to_next_page(); + self.on_page_change(ctx); + return None; } - self.hint.event(ctx, event); } None } @@ -178,10 +268,10 @@ where self.scrollbar.paint(); } if self.scrollbar.has_next_page() { - self.button_back.paint(); - self.hint.paint(); + self.button_prev.paint(); + self.button_next.paint(); } else { - self.buttons.paint(); + self.controls.paint(); } if let Some(val) = self.fade.take() { // Note that this is blocking and takes some time. @@ -194,10 +284,10 @@ where self.scrollbar.bounds(sink); self.content.bounds(sink); if !self.scrollbar.has_next_page() { - self.buttons.bounds(sink); + self.controls.bounds(sink); } else { - self.button_back.bounds(sink); - self.hint.bounds(sink); + self.button_prev.bounds(sink); + self.button_next.bounds(sink); } } } @@ -206,14 +296,14 @@ where impl crate::trace::Trace for SwipePage where T: crate::trace::Trace, - U: crate::trace::Trace, + U: crate::trace::Trace + Component, { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("SwipePage"); t.field("active_page", &self.scrollbar.active_page); t.field("page_count", &self.scrollbar.page_count); t.field("content", &self.content); - t.field("buttons", &self.buttons); + t.field("controls", &self.controls); t.close(); } } @@ -226,43 +316,35 @@ pub struct PageLayout { /// Scroll bar when multiple pages. pub scrollbar: Rect, /// Controls displayed on last page. - pub buttons: Rect, - /// Swipe to continue (unless back button enabled). - pub hint: Rect, - /// Optional back button on every page. - pub hint_button: Rect, - /// Hint area when back button is enabled. - pub hint_button_hint: Rect, + pub controls: Rect, + pub button_prev: Rect, + pub button_next: Rect, } impl PageLayout { - const SCROLLBAR_WIDTH: i16 = 15; + const SCROLLBAR_WIDTH: i16 = 8; const SCROLLBAR_SPACE: i16 = 5; - const HINT_OFF: i16 = 19; pub fn new(area: Rect) -> Self { - let (_, hint) = area.split_bottom(Self::HINT_OFF); - let (buttons, _space) = area.split_right(theme::CONTENT_BORDER); + let (controls, _space) = area.split_right(theme::CONTENT_BORDER); let (_space, content) = area.split_left(theme::CONTENT_BORDER); let (content_single_page, _space) = content.split_right(theme::CONTENT_BORDER); let (content, scrollbar) = content.split_right(Self::SCROLLBAR_SPACE + Self::SCROLLBAR_WIDTH); let (_space, scrollbar) = scrollbar.split_left(Self::SCROLLBAR_SPACE); - let (_, one_row_buttons) = area.split_bottom(theme::button_rows(1)); - let (hint_button, hint_button_hint) = one_row_buttons.split_left(one_row_buttons.height()); - let vertical_inset = (hint_button_hint.height() - Self::HINT_OFF) / 2; - let hint_button_hint = - hint_button_hint.inset(Insets::new(vertical_inset, 0, vertical_inset, 0)); + let (_, one_row_buttons) = area.split_bottom(theme::BUTTON_HEIGHT); + let grid = Grid::new(one_row_buttons, 1, 2).with_spacing(theme::BUTTON_SPACING); + let button_prev = grid.row_col(0, 0); + let button_next = grid.row_col(0, 1); Self { content_single_page, content, scrollbar, - buttons, - hint, - hint_button, - hint_button_hint, + controls, + button_prev, + button_next, } } @@ -288,7 +370,7 @@ where pub fn new(content: T, background: Color) -> Self { let buttons = CancelHold::new(theme::button_confirm()); Self { - inner: SwipePage::new(content, buttons, background), + inner: SwipePage::new(content, buttons, background).with_cancel_on_first_page(), loader: Loader::new(), pad: Pad::with_background(background), } @@ -297,7 +379,7 @@ where pub fn with_danger(content: T, background: Color) -> Self { let buttons = CancelHold::new(theme::button_danger()); Self { - inner: SwipePage::new(content, buttons, background), + inner: SwipePage::new(content, buttons, background).with_cancel_on_first_page(), loader: Loader::new(), pad: Pad::with_background(background), } @@ -348,7 +430,7 @@ where return Some(PageMsg::Controls(CancelConfirmMsg::Confirmed)); } if self.inner.pad.will_paint().is_some() { - self.inner.buttons.request_complete_repaint(ctx); + self.inner.controls.request_complete_repaint(ctx); } None } @@ -365,9 +447,10 @@ where } } if self.inner.scrollbar.has_next_page() { - self.inner.hint.paint(); + self.inner.button_prev.paint(); + self.inner.button_next.paint(); } else { - self.inner.buttons.paint(); + self.inner.controls.paint(); } if let Some(val) = self.inner.fade.take() { // Note that this is blocking and takes some time. @@ -459,7 +542,7 @@ mod tests { page.place(SCREEN); let expected = - " buttons: >"; + " controls: >"; assert_eq!(trace(&page), expected); swipe_up(&mut page); @@ -486,7 +569,7 @@ mod tests { ); page.place(SCREEN); - let expected = " buttons: >"; + let expected = " controls: >"; assert_eq!(trace(&page), expected); swipe_up(&mut page); @@ -509,8 +592,8 @@ mod tests { ); page.place(SCREEN); - let expected1 = " buttons: > >"; - let expected2 = " buttons: > >"; + let expected1 = " controls: > >"; + let expected2 = " controls: > >"; assert_eq!(trace(&page), expected1); swipe_down(&mut page); @@ -545,9 +628,9 @@ mod tests { ); page.place(SCREEN); - let expected1 = " buttons: > >"; - let expected2 = " buttons: > >"; - let expected3 = " buttons: > >"; + let expected1 = " controls: > >"; + let expected2 = " controls: > >"; + let expected3 = " controls: > >"; assert_eq!(trace(&page), expected1); swipe_down(&mut page); @@ -579,9 +662,9 @@ mod tests { ); page.place(SCREEN); - let expected1 = " buttons: > >"; - let expected2 = " buttons: > >"; - let expected3 = " buttons: > >"; + let expected1 = " controls: > >"; + let expected2 = " controls: > >"; + let expected3 = " controls: > >"; assert_eq!(trace(&page), expected1); swipe_up(&mut page); diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 9761a9d624..ab775aca96 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -421,7 +421,7 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M LayoutObj::new(Frame::left_aligned( theme::label_title(), title, - SwipePage::new(paragraphs, buttons, theme::BG), + SwipePage::new(paragraphs, buttons, theme::BG).with_cancel_on_first_page(), ))? }; Ok(obj.into()) @@ -459,7 +459,7 @@ fn confirm_blob( LayoutObj::new(Frame::left_aligned( theme::label_title(), title, - SwipePage::new(paragraphs, buttons, theme::BG), + SwipePage::new(paragraphs, buttons, theme::BG).with_cancel_on_first_page(), ))? } else { panic!("Either `hold=true` or `verb=Some(StrBuffer)` must be specified"); @@ -513,7 +513,9 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut Frame::left_aligned( theme::label_title(), title, - SwipePage::new(paragraphs, buttons, theme::BG).with_swipe_left(), + SwipePage::new(paragraphs, buttons, theme::BG) + .with_swipe_left() + .with_cancel_on_first_page(), ), ))?; Ok(obj.into()) @@ -529,7 +531,7 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m let paragraphs = PropsList::new( items, - &theme::TEXT_BOLD, + &theme::TEXT_DEMIBOLD, &theme::TEXT_NORMAL, &theme::TEXT_MONO, )?; @@ -544,7 +546,8 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m LayoutObj::new(Frame::left_aligned( theme::label_title(), title, - SwipePage::new(paragraphs.into_paragraphs(), buttons, theme::BG), + SwipePage::new(paragraphs.into_paragraphs(), buttons, theme::BG) + .with_cancel_on_first_page(), ))? }; Ok(obj.into()) @@ -743,7 +746,7 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs: let obj = LayoutObj::new(Frame::left_aligned( theme::label_title(), "MODIFY AMOUNT", - SwipePage::new(paragraphs, buttons, theme::BG), + SwipePage::new(paragraphs, buttons, theme::BG).with_cancel_on_first_page(), ))?; Ok(obj.into()) }; @@ -778,7 +781,7 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m let obj = LayoutObj::new(Frame::left_aligned( theme::label_title(), "MODIFY FEE", - SwipePage::new(paragraphs, buttons, theme::BG), + SwipePage::new(paragraphs, buttons, theme::BG).with_cancel_on_first_page(), ))?; Ok(obj.into()) }; @@ -1930,7 +1933,7 @@ mod tests { layout.place(SCREEN); assert_eq!( trace(&layout), - " controls: > 1: > > > >", + " controls: second: