1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-03 03:50:58 +00:00

refactor(core/ui): pagination and buttons on TT

This commit is contained in:
Martin Milata 2023-09-27 00:17:38 +02:00
parent 4f2c639ed7
commit d8e7c00087
14 changed files with 1164 additions and 1523 deletions

View File

@ -0,0 +1 @@
[T2T1] Adjust buttons used for scrolling multipage content.

View File

@ -24,7 +24,7 @@ pub use map::MsgMap;
pub use marquee::Marquee; pub use marquee::Marquee;
pub use maybe::Maybe; pub use maybe::Maybe;
pub use pad::Pad; pub use pad::Pad;
pub use paginated::{AuxPageMsg, PageMsg, Paginate}; pub use paginated::{PageMsg, Paginate};
pub use painter::Painter; pub use painter::Painter;
pub use placed::{FixedHeightBar, Floating, GridPlaced, Split}; pub use placed::{FixedHeightBar, Floating, GridPlaced, Split};
pub use qr_code::Qr; pub use qr_code::Qr;

View File

@ -1,7 +1,13 @@
pub enum AuxPageMsg { /// Common message type for pagination components.
/// Page component was instantiated with BACK button on every page and it pub enum PageMsg<T> {
/// was pressed. /// Pass-through from paged component.
GoBack, Content(T),
/// Confirmed using page controls.
Confirmed,
/// Cancelled using page controls.
Cancelled,
/// Page component was configured to react to swipes and user swiped left. /// Page component was configured to react to swipes and user swiped left.
SwipeLeft, SwipeLeft,
@ -10,19 +16,6 @@ pub enum AuxPageMsg {
SwipeRight, SwipeRight,
} }
/// Common message type for pagination components.
pub enum PageMsg<T, U> {
/// Pass-through from paged component.
Content(T),
/// Messages from page controls outside the paged component, like
/// "OK" and "Cancel" buttons.
Controls(U),
/// Auxilliary events used by exotic pages on touchscreens.
Aux(AuxPageMsg),
}
pub trait Paginate { pub trait Paginate {
/// How many pages of content are there in total? /// How many pages of content are there in total?
fn page_count(&mut self) -> usize; fn page_count(&mut self) -> usize;

View File

@ -162,7 +162,7 @@ where
T: Component + Paginate, T: Component + Paginate,
U: StringType + Clone, U: StringType + Clone,
{ {
type Msg = PageMsg<T::Msg, bool>; type Msg = PageMsg<T::Msg>;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT); let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT);
@ -191,7 +191,7 @@ where
self.change_page(ctx); self.change_page(ctx);
} else { } else {
// Clicked CANCEL. Send result. // Clicked CANCEL. Send result.
return Some(PageMsg::Controls(false)); return Some(PageMsg::Cancelled);
} }
} }
ButtonPos::Right => { ButtonPos::Right => {
@ -201,7 +201,7 @@ where
self.change_page(ctx); self.change_page(ctx);
} else { } else {
// Clicked CONFIRM. Send result. // Clicked CONFIRM. Send result.
return Some(PageMsg::Controls(true)); return Some(PageMsg::Confirmed);
} }
} }
_ => {} _ => {}

View File

@ -93,10 +93,9 @@ where
{ {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg { match msg {
PageMsg::Content(_) => Err(Error::TypeError), PageMsg::Confirmed => Ok(CONFIRMED.as_obj()),
PageMsg::Controls(true) => Ok(CONFIRMED.as_obj()), PageMsg::Cancelled => Ok(CANCELLED.as_obj()),
PageMsg::Controls(false) => Ok(CANCELLED.as_obj()), _ => Err(Error::TypeError),
PageMsg::Aux(_) => Err(Error::TypeError),
} }
} }
} }

View File

@ -1,244 +0,0 @@
use crate::{
time::Instant,
ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, FixedHeightBar, Pad},
geometry::{Grid, Insets, Rect},
util::animation_disabled,
},
};
use super::{theme, Button, ButtonMsg, ButtonStyleSheet, Loader, LoaderMsg};
pub enum HoldToConfirmMsg<T> {
Content(T),
Confirmed,
Cancelled,
}
pub struct HoldToConfirm<T> {
loader: Loader,
content: Child<T>,
buttons: Child<FixedHeightBar<CancelHold>>,
pad: Pad,
}
impl<T> HoldToConfirm<T>
where
T: Component,
{
pub fn new(content: T) -> Self {
Self {
loader: Loader::new(),
content: Child::new(content),
buttons: Child::new(CancelHold::new(theme::button_confirm())),
pad: Pad::with_background(theme::BG),
}
}
pub fn inner(&self) -> &T {
self.content.inner()
}
}
impl<T> Component for HoldToConfirm<T>
where
T: Component,
{
type Msg = HoldToConfirmMsg<T::Msg>;
fn place(&mut self, bounds: Rect) -> Rect {
let controls_area = self.buttons.place(bounds);
let content_area = bounds
.inset(Insets::bottom(controls_area.height()))
.inset(Insets::bottom(theme::BUTTON_SPACING))
.inset(Insets::left(theme::CONTENT_BORDER));
self.pad.place(content_area);
self.loader.place(content_area);
self.content.place(content_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(msg) = self.content.event(ctx, event) {
return Some(HoldToConfirmMsg::Content(msg));
}
let button_msg = match self.buttons.event(ctx, event) {
Some(CancelHoldMsg::Cancelled) => return Some(HoldToConfirmMsg::Cancelled),
Some(CancelHoldMsg::HoldButton(b)) => Some(b),
_ => None,
};
if handle_hold_event(
ctx,
event,
button_msg,
&mut self.loader,
&mut self.pad,
&mut self.content,
) {
return Some(HoldToConfirmMsg::Confirmed);
}
None
}
fn paint(&mut self) {
self.pad.paint();
if self.loader.is_animating() {
self.loader.paint();
} else {
self.content.paint();
}
self.buttons.paint();
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.pad.area);
if self.loader.is_animating() {
self.loader.bounds(sink)
} else {
self.content.bounds(sink)
}
self.buttons.bounds(sink);
}
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for HoldToConfirm<T>
where
T: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("HoldToConfirm");
t.child("content", &self.content);
}
}
pub struct CancelHold {
cancel: Option<Child<Button<&'static str>>>,
hold: Child<Button<&'static str>>,
}
pub enum CancelHoldMsg {
Cancelled,
HoldButton(ButtonMsg),
}
impl CancelHold {
pub fn new(button_style: ButtonStyleSheet) -> FixedHeightBar<Self> {
theme::button_bar(Self {
cancel: Some(Button::with_icon(theme::ICON_CANCEL).into_child()),
hold: Button::with_text("HOLD TO CONFIRM")
.styled(button_style)
.into_child(),
})
}
pub fn with_cancel_arrow() -> FixedHeightBar<Self> {
theme::button_bar(Self {
cancel: Some(Button::with_icon(theme::ICON_UP).into_child()),
hold: Button::with_text("HOLD TO CONFIRM")
.styled(theme::button_confirm())
.into_child(),
})
}
pub fn without_cancel() -> FixedHeightBar<Self> {
theme::button_bar(Self {
cancel: None,
hold: Button::with_text("HOLD TO CONFIRM")
.styled(theme::button_confirm())
.into_child(),
})
}
}
impl Component for CancelHold {
type Msg = CancelHoldMsg;
fn place(&mut self, bounds: Rect) -> Rect {
if self.cancel.is_some() {
let grid = Grid::new(bounds, 1, 4).with_spacing(theme::BUTTON_SPACING);
self.cancel.place(grid.row_col(0, 0));
self.hold
.place(grid.row_col(0, 1).union(grid.row_col(0, 3)));
} else {
self.hold.place(bounds);
};
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(ButtonMsg::Clicked) = self.cancel.event(ctx, event) {
return Some(CancelHoldMsg::Cancelled);
}
self.hold.event(ctx, event).map(CancelHoldMsg::HoldButton)
}
fn paint(&mut self) {
self.cancel.paint();
self.hold.paint();
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.cancel.bounds(sink);
self.hold.bounds(sink);
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for CancelHold {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("CancelHold");
}
}
/// Hold-to-confirm logic to be called from event handler of the component that
/// owns `pad`, `loader`, and `content` and a Button. It is expected that the
/// associated button already processed `event` and returned `button_msg`.
/// Returns `true` when the interaction successfully finished.
#[must_use]
pub fn handle_hold_event<T>(
ctx: &mut EventCtx,
event: Event,
button_msg: Option<ButtonMsg>,
loader: &mut Loader,
pad: &mut Pad,
content: &mut T,
) -> bool
where
T: Component,
{
let now = Instant::now();
if let Some(LoaderMsg::ShrunkCompletely) = loader.event(ctx, event) {
// Clear the remnants of the loader.
pad.clear();
// Switch it to the initial state, so we stop painting it.
loader.reset();
// Re-draw the whole content tree.
content.request_complete_repaint(ctx);
// This can be a result of an animation frame event, we should take
// care to not short-circuit here and deliver the event to the
// content as well.
}
match button_msg {
Some(ButtonMsg::Pressed) => {
loader.start_growing(ctx, now);
pad.clear(); // Clear the remnants of the content.
}
Some(ButtonMsg::Released) => {
loader.start_shrinking(ctx, now);
}
Some(ButtonMsg::Clicked) => {
if loader.is_completely_grown(now) || animation_disabled() {
return true;
} else {
loader.start_shrinking(ctx, now);
}
}
_ => {}
}
false
}

View File

@ -7,10 +7,8 @@ mod fido;
mod fido_icons; mod fido_icons;
mod error; mod error;
mod frame; mod frame;
mod hold_to_confirm;
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
mod homescreen; mod homescreen;
mod horizontal_page;
mod keyboard; mod keyboard;
mod loader; mod loader;
mod number_input; mod number_input;
@ -18,6 +16,7 @@ mod page;
mod progress; mod progress;
mod result; mod result;
mod scroll; mod scroll;
mod simple_page;
mod swipe; mod swipe;
mod welcome_screen; mod welcome_screen;
@ -31,10 +30,8 @@ pub use dialog::{Dialog, DialogMsg, IconDialog};
pub use error::ErrorScreen; pub use error::ErrorScreen;
pub use fido::{FidoConfirm, FidoMsg}; pub use fido::{FidoConfirm, FidoMsg};
pub use frame::{Frame, FrameMsg}; pub use frame::{Frame, FrameMsg};
pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
pub use homescreen::{Homescreen, HomescreenMsg, Lockscreen}; pub use homescreen::{Homescreen, HomescreenMsg, Lockscreen};
pub use horizontal_page::HorizontalPage;
pub use keyboard::{ pub use keyboard::{
bip39::Bip39Input, bip39::Bip39Input,
mnemonic::{MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg}, mnemonic::{MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg},
@ -45,10 +42,11 @@ pub use keyboard::{
}; };
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet}; pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
pub use number_input::{NumberInputDialog, NumberInputDialogMsg}; pub use number_input::{NumberInputDialog, NumberInputDialogMsg};
pub use page::{SwipeHoldPage, SwipePage}; pub use page::ButtonPage;
pub use progress::Progress; pub use progress::Progress;
pub use result::{ResultFooter, ResultScreen, ResultStyle}; pub use result::{ResultFooter, ResultScreen, ResultStyle};
pub use scroll::ScrollBar; pub use scroll::ScrollBar;
pub use simple_page::SimplePage;
pub use swipe::{Swipe, SwipeDirection}; pub use swipe::{Swipe, SwipeDirection};
pub use welcome_screen::WelcomeScreen; pub use welcome_screen::WelcomeScreen;

File diff suppressed because it is too large Load Diff

View File

@ -56,11 +56,17 @@ impl ScrollBar {
} }
pub fn go_to_next_page(&mut self) { pub fn go_to_next_page(&mut self) {
self.go_to(self.active_page.saturating_add(1).min(self.page_count - 1)); self.go_to_relative(1)
} }
pub fn go_to_previous_page(&mut self) { pub fn go_to_previous_page(&mut self) {
self.go_to(self.active_page.saturating_sub(1)); self.go_to_relative(-1)
}
pub fn go_to_relative(&mut self, step: isize) {
self.go_to(
(self.active_page as isize + step).clamp(0, self.page_count as isize - 1) as usize,
);
} }
pub fn go_to(&mut self, active_page: usize) { pub fn go_to(&mut self, active_page: usize) {

View File

@ -1,9 +1,7 @@
use crate::ui::{ use crate::ui::{
component::{ component::{base::ComponentExt, Component, Event, EventCtx, Pad, PageMsg, Paginate},
base::ComponentExt, AuxPageMsg, Component, Event, EventCtx, Never, Pad, PageMsg, Paginate,
},
display::{self, Color}, display::{self, Color},
geometry::{Insets, Rect}, geometry::{Axis, Insets, Rect},
}; };
use super::{theme, ScrollBar, Swipe, SwipeDirection}; use super::{theme, ScrollBar, Swipe, SwipeDirection};
@ -11,26 +9,40 @@ use super::{theme, ScrollBar, Swipe, SwipeDirection};
const SCROLLBAR_HEIGHT: i16 = 18; const SCROLLBAR_HEIGHT: i16 = 18;
const SCROLLBAR_BORDER: i16 = 4; const SCROLLBAR_BORDER: i16 = 4;
pub struct HorizontalPage<T> { pub struct SimplePage<T> {
content: T, content: T,
pad: Pad, pad: Pad,
swipe: Swipe, swipe: Swipe,
scrollbar: ScrollBar, scrollbar: ScrollBar,
axis: Axis,
swipe_right_to_go_back: bool, swipe_right_to_go_back: bool,
fade: Option<u16>, fade: Option<u16>,
} }
impl<T> HorizontalPage<T> impl<T> SimplePage<T>
where where
T: Paginate, T: Paginate,
T: Component, T: Component,
{ {
pub fn new(content: T, background: Color) -> Self { pub fn horizontal(content: T, background: Color) -> Self {
Self { Self {
content, content,
swipe: Swipe::new(), swipe: Swipe::new(),
pad: Pad::with_background(background), pad: Pad::with_background(background),
scrollbar: ScrollBar::horizontal(), scrollbar: ScrollBar::horizontal(),
axis: Axis::Horizontal,
swipe_right_to_go_back: false,
fade: None,
}
}
pub fn vertical(content: T, background: Color) -> Self {
Self {
content,
swipe: Swipe::new(),
pad: Pad::with_background(background),
scrollbar: ScrollBar::vertical(),
axis: Axis::Vertical,
swipe_right_to_go_back: false, swipe_right_to_go_back: false,
fade: None, fade: None,
} }
@ -46,11 +58,20 @@ where
} }
fn setup_swipe(&mut self) { fn setup_swipe(&mut self) {
self.swipe.allow_left = self.scrollbar.has_next_page(); if self.is_horizontal() {
self.swipe.allow_right = self.scrollbar.has_previous_page() || self.swipe_right_to_go_back; self.swipe.allow_left = self.scrollbar.has_next_page();
self.swipe.allow_right =
self.scrollbar.has_previous_page() || self.swipe_right_to_go_back;
} else {
self.swipe.allow_up = self.scrollbar.has_next_page();
self.swipe.allow_down = self.scrollbar.has_previous_page();
self.swipe.allow_right = self.swipe_right_to_go_back;
}
} }
fn on_page_change(&mut self, ctx: &mut EventCtx) { fn change_page(&mut self, ctx: &mut EventCtx, step: isize) {
// Advance scrollbar.
self.scrollbar.go_to_relative(step);
// Adjust the swipe parameters according to the scrollbar. // Adjust the swipe parameters according to the scrollbar.
self.setup_swipe(); self.setup_swipe();
@ -64,23 +85,43 @@ where
// paint. // paint.
self.fade = Some(theme::BACKLIGHT_NORMAL); self.fade = Some(theme::BACKLIGHT_NORMAL);
} }
fn is_horizontal(&self) -> bool {
matches!(self.axis, Axis::Horizontal)
}
} }
impl<T> Component for HorizontalPage<T> impl<T> Component for SimplePage<T>
where where
T: Paginate, T: Paginate,
T: Component, T: Component,
{ {
type Msg = PageMsg<T::Msg, Never>; type Msg = PageMsg<T::Msg>;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
self.swipe.place(bounds); self.swipe.place(bounds);
let (content, scrollbar) = bounds.split_bottom(SCROLLBAR_HEIGHT + SCROLLBAR_BORDER); let (content, scrollbar) = if self.is_horizontal() {
self.pad.place(content); bounds.split_bottom(SCROLLBAR_HEIGHT + SCROLLBAR_BORDER)
self.content.place(content); } else {
self.scrollbar bounds.split_right(SCROLLBAR_HEIGHT + SCROLLBAR_BORDER)
.place(scrollbar.inset(Insets::bottom(SCROLLBAR_BORDER))); };
self.content.place(bounds);
if self.content.page_count() > 1 {
self.pad.place(content);
self.content.place(content);
} else {
self.pad.place(bounds);
}
if self.is_horizontal() {
self.scrollbar
.place(scrollbar.inset(Insets::bottom(SCROLLBAR_BORDER)));
} else {
self.scrollbar
.place(scrollbar.inset(Insets::right(SCROLLBAR_BORDER)));
}
self.scrollbar self.scrollbar
.set_count_and_active_page(self.content.page_count(), 0); .set_count_and_active_page(self.content.page_count(), 0);
@ -92,18 +133,19 @@ where
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
ctx.set_page_count(self.scrollbar.page_count); ctx.set_page_count(self.scrollbar.page_count);
if let Some(swipe) = self.swipe.event(ctx, event) { if let Some(swipe) = self.swipe.event(ctx, event) {
match swipe { match (swipe, self.axis) {
SwipeDirection::Left => { (SwipeDirection::Left, Axis::Horizontal) | (SwipeDirection::Up, Axis::Vertical) => {
self.scrollbar.go_to_next_page(); self.change_page(ctx, 1);
self.on_page_change(ctx);
return None; return None;
} }
SwipeDirection::Right => { (SwipeDirection::Right, _)
if self.swipe_right_to_go_back && self.scrollbar.active_page == 0 { if self.swipe_right_to_go_back && self.scrollbar.active_page == 0 =>
return Some(PageMsg::Aux(AuxPageMsg::GoBack)); {
} return Some(PageMsg::Cancelled);
self.scrollbar.go_to_previous_page(); }
self.on_page_change(ctx); (SwipeDirection::Right, Axis::Horizontal)
| (SwipeDirection::Down, Axis::Vertical) => {
self.change_page(ctx, -1);
return None; return None;
} }
_ => { _ => {
@ -117,7 +159,9 @@ where
fn paint(&mut self) { fn paint(&mut self) {
self.pad.paint(); self.pad.paint();
self.content.paint(); self.content.paint();
self.scrollbar.paint(); if self.scrollbar.has_pages() {
self.scrollbar.paint();
}
if let Some(val) = self.fade.take() { if let Some(val) = self.fade.take() {
// Note that this is blocking and takes some time. // Note that this is blocking and takes some time.
display::fade_backlight(val); display::fade_backlight(val);
@ -133,12 +177,12 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for HorizontalPage<T> impl<T> crate::trace::Trace for SimplePage<T>
where where
T: crate::trace::Trace, T: crate::trace::Trace,
{ {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("HorizontalPage"); t.component("SimplePage");
t.int("active_page", self.scrollbar.active_page as i64); t.int("active_page", self.scrollbar.active_page as i64);
t.int("page_count", self.scrollbar.page_count as i64); t.int("page_count", self.scrollbar.page_count as i64);
t.child("content", &self.content); t.child("content", &self.content);

View File

@ -20,7 +20,7 @@ use crate::{
component::{ component::{
base::ComponentExt, base::ComponentExt,
image::BlendedImage, image::BlendedImage,
paginated::{AuxPageMsg, PageMsg, Paginate}, paginated::{PageMsg, Paginate},
painter, painter,
placed::GridPlaced, placed::GridPlaced,
text::{ text::{
@ -48,13 +48,12 @@ use crate::{
use super::{ use super::{
component::{ component::{
AddressDetails, Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, AddressDetails, Bip39Input, Button, ButtonMsg, ButtonPage, ButtonStyleSheet,
CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm, FidoMsg, Frame, CancelConfirmMsg, CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm,
FrameMsg, HoldToConfirm, HoldToConfirmMsg, Homescreen, HomescreenMsg, HorizontalPage, FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, IconDialog, Lockscreen, MnemonicInput,
IconDialog, Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputDialog, NumberInputDialogMsg,
NumberInputDialog, NumberInputDialogMsg, PassphraseKeyboard, PassphraseKeyboardMsg, PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
PinKeyboard, PinKeyboardMsg, Progress, SelectWordCount, SelectWordCountMsg, SelectWordMsg, SelectWordCount, SelectWordCountMsg, SelectWordMsg, SimplePage, Slip39Input, WelcomeScreen,
Slip39Input, SwipeHoldPage, SwipePage, WelcomeScreen,
}, },
constant, theme, constant, theme,
}; };
@ -144,19 +143,6 @@ where
} }
} }
impl<T> ComponentMsgObj for HoldToConfirm<T>
where
T: ComponentMsgObj,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
HoldToConfirmMsg::Content(c) => Ok(self.inner().msg_try_into_obj(c)?),
HoldToConfirmMsg::Confirmed => Ok(CONFIRMED.as_obj()),
HoldToConfirmMsg::Cancelled => Ok(CANCELLED.as_obj()),
}
}
}
impl<T> ComponentMsgObj for PinKeyboard<T> impl<T> ComponentMsgObj for PinKeyboard<T>
where where
T: AsRef<str>, T: AsRef<str>,
@ -209,34 +195,18 @@ where
} }
} }
impl<T, U> ComponentMsgObj for SwipePage<T, U> impl<T, U> ComponentMsgObj for ButtonPage<T, U>
where where
T: Component + Paginate, T: Component + Paginate,
U: Component, U: AsRef<str> + From<&'static str>,
<U as Component>::Msg: TryInto<Obj, Error = Error>,
{ {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg { match msg {
PageMsg::Content(_) => Err(Error::TypeError), PageMsg::Content(_) => Err(Error::TypeError),
PageMsg::Controls(msg) => msg.try_into(), PageMsg::Confirmed => Ok(CONFIRMED.as_obj()),
PageMsg::Aux(AuxPageMsg::GoBack) => Ok(CANCELLED.as_obj()), PageMsg::Cancelled => Ok(CANCELLED.as_obj()),
PageMsg::Aux(AuxPageMsg::SwipeLeft) => Ok(INFO.as_obj()), PageMsg::SwipeLeft => Ok(INFO.as_obj()),
PageMsg::Aux(AuxPageMsg::SwipeRight) => Ok(CANCELLED.as_obj()), PageMsg::SwipeRight => Ok(CANCELLED.as_obj()),
}
}
}
impl<T> ComponentMsgObj for SwipeHoldPage<T>
where
T: Component + Paginate,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
PageMsg::Content(_) => Err(Error::TypeError),
PageMsg::Controls(msg) => msg.try_into(),
PageMsg::Aux(AuxPageMsg::GoBack) => Ok(CANCELLED.as_obj()),
PageMsg::Aux(AuxPageMsg::SwipeLeft) => Ok(INFO.as_obj()),
PageMsg::Aux(AuxPageMsg::SwipeRight) => Ok(CANCELLED.as_obj()),
} }
} }
} }
@ -361,16 +331,15 @@ impl ComponentMsgObj for Qr {
} }
} }
impl<T> ComponentMsgObj for HorizontalPage<T> impl<T> ComponentMsgObj for SimplePage<T>
where where
T: ComponentMsgObj + Paginate, T: ComponentMsgObj + Paginate,
{ {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg { match msg {
PageMsg::Content(inner_msg) => Ok(self.inner().msg_try_into_obj(inner_msg)?), PageMsg::Content(inner_msg) => Ok(self.inner().msg_try_into_obj(inner_msg)?),
PageMsg::Controls(_) => Err(Error::TypeError), PageMsg::Cancelled => Ok(CANCELLED.as_obj()),
PageMsg::Aux(AuxPageMsg::GoBack) => Ok(CANCELLED.as_obj()), _ => Err(Error::TypeError),
PageMsg::Aux(_) => Err(Error::TypeError),
} }
} }
} }
@ -428,21 +397,15 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
paragraphs.into_paragraphs() paragraphs.into_paragraphs()
}; };
let obj = if hold { let mut page = if hold {
let page = if hold_danger { ButtonPage::new(paragraphs, theme::BG).with_hold()
SwipeHoldPage::with_danger(paragraphs, theme::BG)
} else {
SwipeHoldPage::new(paragraphs, theme::BG)
};
LayoutObj::new(Frame::left_aligned(theme::label_title(), title, page))?
} else { } else {
let buttons = Button::cancel_confirm_text(verb_cancel, verb); ButtonPage::new(paragraphs, theme::BG).with_cancel_confirm(verb_cancel, verb)
LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
SwipePage::new(paragraphs, buttons, theme::BG).with_cancel_on_first_page(),
))?
}; };
if hold && hold_danger {
page = page.with_confirm_style(theme::button_danger())
}
let obj = LayoutObj::new(Frame::left_aligned(theme::label_title(), title, page))?;
Ok(obj.into()) Ok(obj.into())
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -472,15 +435,11 @@ extern "C" fn new_confirm_emphasized(n_args: usize, args: *const Obj, kwargs: *m
} }
} }
let buttons = Button::<StrBuffer>::cancel_confirm_text(None, verb);
let obj = LayoutObj::new(Frame::left_aligned( let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(), theme::label_title(),
title, title,
SwipePage::new( ButtonPage::new(FormattedText::new(ops).vertically_centered(), theme::BG)
FormattedText::new(ops).vertically_centered(), .with_cancel_confirm(None, verb),
buttons,
theme::BG,
),
))?; ))?;
Ok(obj.into()) Ok(obj.into())
}; };
@ -558,36 +517,21 @@ impl ConfirmBlobParams {
} }
.into_paragraphs(); .into_paragraphs();
let obj = if self.hold { let page: ButtonPage<_, StrBuffer> = if self.hold {
let mut frame = Frame::left_aligned( ButtonPage::new(paragraphs, theme::BG).with_hold()
theme::label_title(),
self.title,
SwipeHoldPage::new(paragraphs, theme::BG),
);
if let Some(subtitle) = self.subtitle {
frame = frame.with_subtitle(theme::label_subtitle(), subtitle);
}
if self.info_button {
frame = frame.with_info_button();
}
LayoutObj::new(frame)?
} else if let Some(verb) = self.verb { } else if let Some(verb) = self.verb {
let buttons = Button::cancel_confirm_text(self.verb_cancel, Some(verb)); ButtonPage::new(paragraphs, theme::BG).with_cancel_confirm(self.verb_cancel, Some(verb))
let mut frame = Frame::left_aligned(
theme::label_title(),
self.title,
SwipePage::new(paragraphs, buttons, theme::BG).with_cancel_on_first_page(),
);
if let Some(subtitle) = self.subtitle {
frame = frame.with_subtitle(theme::label_subtitle(), subtitle);
}
if self.info_button {
frame = frame.with_info_button();
}
LayoutObj::new(frame)?
} else { } else {
panic!("Either `hold=true` or `verb=Some(StrBuffer)` must be specified"); panic!("Either `hold=true` or `verb=Some(StrBuffer)` must be specified");
}; };
let mut frame = Frame::left_aligned(theme::label_title(), self.title, page);
if let Some(subtitle) = self.subtitle {
frame = frame.with_subtitle(theme::label_subtitle(), subtitle);
}
if self.info_button {
frame = frame.with_info_button();
}
let obj = LayoutObj::new(frame)?;
Ok(obj.into()) Ok(obj.into())
} }
} }
@ -649,14 +593,13 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
} }
.into_paragraphs(); .into_paragraphs();
let buttons = Button::cancel_confirm_text(None, Some("CONFIRM"));
let obj = LayoutObj::new( let obj = LayoutObj::new(
Frame::left_aligned( Frame::left_aligned(
theme::label_title(), theme::label_title(),
title, title,
SwipePage::new(paragraphs, buttons, theme::BG) ButtonPage::new(paragraphs, theme::BG)
.with_swipe_left() .with_swipe_left()
.with_cancel_on_first_page(), .with_cancel_confirm(None, Some("CONFIRM")),
) )
.with_info_button(), .with_info_button(),
)?; )?;
@ -677,21 +620,13 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
&theme::TEXT_MONO, &theme::TEXT_MONO,
&theme::TEXT_MONO, &theme::TEXT_MONO,
)?; )?;
let obj = if hold { let page: ButtonPage<_, StrBuffer> = if hold {
LayoutObj::new(Frame::left_aligned( ButtonPage::new(paragraphs.into_paragraphs(), theme::BG).with_hold()
theme::label_title(),
title,
SwipeHoldPage::new(paragraphs.into_paragraphs(), theme::BG),
))?
} else { } else {
let buttons = Button::cancel_confirm_text(None, Some("CONFIRM")); ButtonPage::new(paragraphs.into_paragraphs(), theme::BG)
LayoutObj::new(Frame::left_aligned( .with_cancel_confirm(None, Some("CONFIRM".into()))
theme::label_title(),
title,
SwipePage::new(paragraphs.into_paragraphs(), buttons, theme::BG)
.with_cancel_on_first_page(),
))?
}; };
let obj = LayoutObj::new(Frame::left_aligned(theme::label_title(), title, page))?;
Ok(obj.into()) Ok(obj.into())
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -789,7 +724,8 @@ extern "C" fn new_show_address_details(n_args: usize, args: *const Obj, kwargs:
ad.add_xpub(xtitle, text)?; ad.add_xpub(xtitle, text)?;
} }
let obj = LayoutObj::new(HorizontalPage::new(ad, theme::BG).with_swipe_right_to_go_back())?; let obj =
LayoutObj::new(SimplePage::horizontal(ad, theme::BG).with_swipe_right_to_go_back())?;
Ok(obj.into()) Ok(obj.into())
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -814,7 +750,8 @@ extern "C" fn new_show_info_with_cancel(n_args: usize, args: *const Obj, kwargs:
Frame::left_aligned( Frame::left_aligned(
theme::label_title(), theme::label_title(),
title, title,
SwipePage::new(paragraphs.into_paragraphs(), Empty, theme::BG).with_swipe_right(), SimplePage::vertical(paragraphs.into_paragraphs(), theme::BG)
.with_swipe_right_to_go_back(),
) )
.with_cancel_button(), .with_cancel_button(),
)?; )?;
@ -866,11 +803,11 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, label)); paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, label));
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value)); paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value));
} }
let mut page = if cancel_arrow { let mut page: ButtonPage<_, StrBuffer> =
SwipeHoldPage::with_cancel_arrow(paragraphs.into_paragraphs(), theme::BG) ButtonPage::new(paragraphs.into_paragraphs(), theme::BG).with_hold();
} else { if cancel_arrow {
SwipeHoldPage::new(paragraphs.into_paragraphs(), theme::BG) page = page.with_cancel_arrow()
}; }
if info_button { if info_button {
page = page.with_swipe_left(); page = page.with_swipe_left();
} }
@ -903,11 +840,11 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs:
Paragraph::new(&theme::TEXT_MONO, amount_new), Paragraph::new(&theme::TEXT_MONO, amount_new),
]); ]);
let buttons = Button::cancel_confirm_text(Some("^"), Some("CONTINUE"));
let obj = LayoutObj::new(Frame::left_aligned( let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(), theme::label_title(),
"MODIFY AMOUNT", "MODIFY AMOUNT",
SwipePage::new(paragraphs, buttons, theme::BG), ButtonPage::<_, StrBuffer>::new(paragraphs, theme::BG)
.with_cancel_confirm(Some("^".into()), Some("CONTINUE".into())),
))?; ))?;
Ok(obj.into()) Ok(obj.into())
}; };
@ -942,7 +879,9 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m
Frame::left_aligned( Frame::left_aligned(
theme::label_title(), theme::label_title(),
title, title,
SwipeHoldPage::new(paragraphs, theme::BG).with_swipe_left(), ButtonPage::<_, StrBuffer>::new(paragraphs, theme::BG)
.with_hold()
.with_swipe_left(),
) )
.with_info_button(), .with_info_button(),
)?; )?;
@ -1241,15 +1180,13 @@ extern "C" fn new_confirm_more(n_args: usize, args: *const Obj, kwargs: *mut Map
paragraphs.add(Paragraph::new(style, text)); paragraphs.add(Paragraph::new(style, text));
} }
let button =
theme::button_bar(Button::with_text(button).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
}));
let obj = LayoutObj::new(Frame::left_aligned( let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(), theme::label_title(),
title, title,
SwipePage::new(paragraphs.into_paragraphs(), button, theme::BG).with_back_button(), ButtonPage::new(paragraphs.into_paragraphs(), theme::BG)
.with_cancel_confirm(None, Some(button))
.with_confirm_style(theme::button_default())
.with_back_button(),
))?; ))?;
Ok(obj.into()) Ok(obj.into())
}; };
@ -1271,7 +1208,7 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut
let obj = LayoutObj::new(Frame::left_aligned( let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(), theme::label_title(),
"AUTHORIZE COINJOIN", "AUTHORIZE COINJOIN",
SwipeHoldPage::new(paragraphs, theme::BG), ButtonPage::<_, StrBuffer>::new(paragraphs, theme::BG).with_hold(),
))?; ))?;
Ok(obj.into()) Ok(obj.into())
}; };
@ -1331,12 +1268,10 @@ extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map)
let words: [StrBuffer; 3] = iter_into_array(words_iterable)?; let words: [StrBuffer; 3] = iter_into_array(words_iterable)?;
let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_DEMIBOLD, description)]); let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_DEMIBOLD, description)]);
let buttons = Button::select_word(words);
let obj = LayoutObj::new(Frame::left_aligned( let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(), theme::label_title(),
title, title,
SwipePage::new(paragraphs, buttons, theme::BG), Dialog::new(paragraphs, Button::select_word(words)),
))?; ))?;
Ok(obj.into()) Ok(obj.into())
}; };
@ -1357,7 +1292,9 @@ extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut
let obj = LayoutObj::new(Frame::left_aligned( let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(), theme::label_title(),
title, title,
SwipeHoldPage::without_cancel(paragraphs.into_paragraphs(), theme::BG), ButtonPage::<_, StrBuffer>::new(paragraphs.into_paragraphs(), theme::BG)
.with_hold()
.without_cancel(),
))?; ))?;
Ok(obj.into()) Ok(obj.into())
}; };
@ -1535,13 +1472,10 @@ extern "C" fn new_show_remaining_shares(n_args: usize, args: *const Obj, kwargs:
let obj = LayoutObj::new(Frame::left_aligned( let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(), theme::label_title(),
"REMAINING SHARES", "REMAINING SHARES",
SwipePage::new( ButtonPage::<_, StrBuffer>::new(paragraphs.into_paragraphs(), theme::BG)
paragraphs.into_paragraphs(), .with_cancel_confirm(None, Some("CONTINUE".into()))
theme::button_bar(Button::with_text("CONTINUE").map(|msg| { .with_confirm_style(theme::button_default())
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) .without_cancel(),
})),
theme::BG,
),
))?; ))?;
Ok(obj.into()) Ok(obj.into())
}; };

View File

@ -1173,6 +1173,7 @@ async def confirm_signverify(
title, title,
message, message,
"Confirm message:", "Confirm message:",
hold=not verify,
br_code=BR_TYPE_OTHER, br_code=BR_TYPE_OTHER,
) )

View File

@ -262,7 +262,7 @@ class InputFlowShowAddressQRCode(InputFlowBase):
yield yield
self.debug.click(buttons.CORNER_BUTTON, wait=True) self.debug.click(buttons.CORNER_BUTTON, wait=True)
# synchronize; TODO get rid of this once we have single-global-layout # synchronize; TODO get rid of this once we have single-global-layout
self.debug.synchronize_at("HorizontalPage") self.debug.synchronize_at("SimplePage")
self.debug.swipe_left(wait=True) self.debug.swipe_left(wait=True)
self.debug.swipe_right(wait=True) self.debug.swipe_right(wait=True)
@ -302,7 +302,7 @@ class InputFlowShowAddressQRCodeCancel(InputFlowBase):
yield yield
self.debug.click(buttons.CORNER_BUTTON, wait=True) self.debug.click(buttons.CORNER_BUTTON, wait=True)
# synchronize; TODO get rid of this once we have single-global-layout # synchronize; TODO get rid of this once we have single-global-layout
self.debug.synchronize_at("HorizontalPage") self.debug.synchronize_at("SimplePage")
self.debug.swipe_left(wait=True) self.debug.swipe_left(wait=True)
self.debug.click(buttons.CORNER_BUTTON, wait=True) self.debug.click(buttons.CORNER_BUTTON, wait=True)
@ -427,7 +427,7 @@ class InputFlowShowXpubQRCode(InputFlowBase):
self.debug.click(buttons.CORNER_BUTTON, wait=True) self.debug.click(buttons.CORNER_BUTTON, wait=True)
# synchronize; TODO get rid of this once we have single-global-layout # synchronize; TODO get rid of this once we have single-global-layout
self.debug.synchronize_at("HorizontalPage") self.debug.synchronize_at("SimplePage")
self.debug.swipe_left(wait=True) self.debug.swipe_left(wait=True)
self.debug.swipe_right(wait=True) self.debug.swipe_right(wait=True)

File diff suppressed because it is too large Load Diff