mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-05 17:56:10 +00:00
refactor(core/rust/ui): hold-to-confirm for paged content
[no changelog]
This commit is contained in:
parent
24a1f2e25e
commit
ee1497b87e
@ -33,7 +33,7 @@ impl<T> Animation<T> {
|
||||
T: Lerp,
|
||||
{
|
||||
let factor = self.elapsed(now) / self.duration;
|
||||
T::lerp(self.from, self.to, factor)
|
||||
T::lerp_bounded(self.from, self.to, factor)
|
||||
}
|
||||
|
||||
/// Seek the animation such that `value` would be the current value.
|
||||
|
@ -351,16 +351,15 @@ impl<T> Button<T> {
|
||||
F1: Fn(ButtonMsg) -> Option<R>,
|
||||
T: AsRef<str>,
|
||||
{
|
||||
const BUTTON_SPACING: i32 = 6;
|
||||
(
|
||||
GridPlaced::new(left)
|
||||
.with_grid(1, 3)
|
||||
.with_spacing(BUTTON_SPACING)
|
||||
.with_spacing(theme::BUTTON_SPACING)
|
||||
.with_row_col(0, 0)
|
||||
.map(left_map),
|
||||
GridPlaced::new(right)
|
||||
.with_grid(1, 3)
|
||||
.with_spacing(BUTTON_SPACING)
|
||||
.with_spacing(theme::BUTTON_SPACING)
|
||||
.with_from_to((0, 1), (0, 2))
|
||||
.map(right_map),
|
||||
)
|
||||
|
@ -1,136 +0,0 @@
|
||||
use crate::{
|
||||
time::Instant,
|
||||
ui::{
|
||||
component::{Child, Component, ComponentExt, Event, EventCtx, Pad},
|
||||
geometry::Rect,
|
||||
model_tt::component::DialogLayout,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{theme, Button, ButtonMsg, Loader, LoaderMsg};
|
||||
|
||||
pub enum HoldToConfirmMsg<T> {
|
||||
Content(T),
|
||||
Confirmed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
pub struct HoldToConfirm<T> {
|
||||
loader: Loader,
|
||||
content: Child<T>,
|
||||
cancel: Child<Button<&'static str>>,
|
||||
confirm: Child<Button<&'static str>>,
|
||||
pad: Pad,
|
||||
}
|
||||
|
||||
impl<T> HoldToConfirm<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
pub fn new(content: T) -> Self {
|
||||
Self {
|
||||
loader: Loader::new(0),
|
||||
content: Child::new(content),
|
||||
cancel: Child::new(Button::with_text("Cancel")),
|
||||
confirm: Child::new(Button::with_text("Hold")),
|
||||
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 layout = DialogLayout::middle(bounds);
|
||||
self.loader.place(layout.content);
|
||||
self.content.place(layout.content);
|
||||
let (left, right) = layout.controls.split_left(layout.controls.size().x);
|
||||
self.cancel.place(left);
|
||||
self.confirm.place(right);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let now = Instant::now();
|
||||
|
||||
if let Some(LoaderMsg::ShrunkCompletely) = self.loader.event(ctx, event) {
|
||||
// Clear the remnants of the loader.
|
||||
self.pad.clear();
|
||||
// Switch it to the initial state, so we stop painting it.
|
||||
self.loader.reset();
|
||||
// Re-draw the whole content tree.
|
||||
self.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.
|
||||
}
|
||||
if let Some(msg) = self.content.event(ctx, event) {
|
||||
return Some(Self::Msg::Content(msg));
|
||||
}
|
||||
if let Some(ButtonMsg::Clicked) = self.cancel.event(ctx, event) {
|
||||
return Some(Self::Msg::Cancelled);
|
||||
}
|
||||
match self.confirm.event(ctx, event) {
|
||||
Some(ButtonMsg::Pressed) => {
|
||||
self.loader.start_growing(ctx, now);
|
||||
self.pad.clear(); // Clear the remnants of the content.
|
||||
}
|
||||
Some(ButtonMsg::Released) => {
|
||||
self.loader.start_shrinking(ctx, now);
|
||||
}
|
||||
Some(ButtonMsg::Clicked) => {
|
||||
if self.loader.is_completely_grown(now) {
|
||||
self.loader.reset();
|
||||
return Some(HoldToConfirmMsg::Confirmed);
|
||||
} else {
|
||||
self.loader.start_shrinking(ctx, now);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
self.pad.paint();
|
||||
if self.loader.is_animating() {
|
||||
self.loader.paint();
|
||||
} else {
|
||||
self.content.paint();
|
||||
}
|
||||
self.cancel.paint();
|
||||
self.confirm.paint();
|
||||
}
|
||||
|
||||
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.cancel.bounds(sink);
|
||||
self.confirm.bounds(sink);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for HoldToConfirm<T>
|
||||
where
|
||||
T: crate::trace::Trace,
|
||||
{
|
||||
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
|
||||
d.open("HoldToConfirm");
|
||||
self.content.trace(d);
|
||||
d.close();
|
||||
}
|
||||
}
|
217
core/embed/rust/src/ui/model_tt/component/hold_to_confirm.rs
Normal file
217
core/embed/rust/src/ui/model_tt/component/hold_to_confirm.rs
Normal file
@ -0,0 +1,217 @@
|
||||
use crate::{
|
||||
time::Instant,
|
||||
ui::{
|
||||
component::{Child, Component, ComponentExt, Event, EventCtx, Pad},
|
||||
geometry::{Grid, Rect},
|
||||
model_tt::component::DialogLayout,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{theme, Button, ButtonMsg, Loader, LoaderMsg};
|
||||
|
||||
pub enum HoldToConfirmMsg<T> {
|
||||
Content(T),
|
||||
Confirmed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
pub struct HoldToConfirm<T> {
|
||||
loader: Loader,
|
||||
content: Child<T>,
|
||||
buttons: 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: CancelHold::new(),
|
||||
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 layout = DialogLayout::middle(bounds);
|
||||
self.pad.place(layout.content);
|
||||
self.loader.place(layout.content);
|
||||
self.content.place(layout.content);
|
||||
self.buttons.place(layout.controls);
|
||||
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();
|
||||
}
|
||||
|
||||
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, d: &mut dyn crate::trace::Tracer) {
|
||||
d.open("HoldToConfirm");
|
||||
self.content.trace(d);
|
||||
d.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CancelHold {
|
||||
cancel: Button<&'static str>,
|
||||
hold: Button<&'static str>,
|
||||
}
|
||||
|
||||
pub enum CancelHoldMsg {
|
||||
Cancelled,
|
||||
HoldButton(ButtonMsg),
|
||||
}
|
||||
|
||||
impl CancelHold {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cancel: Button::with_icon(theme::ICON_CANCEL),
|
||||
hold: Button::with_text("HOLD TO CONFIRM").styled(theme::button_confirm()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for CancelHold {
|
||||
type Msg = CancelHoldMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
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)));
|
||||
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();
|
||||
}
|
||||
|
||||
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, d: &mut dyn crate::trace::Tracer) {
|
||||
d.string("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) {
|
||||
loader.reset();
|
||||
return true;
|
||||
} else {
|
||||
loader.start_shrinking(ctx, now);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
@ -5,6 +5,7 @@ use crate::{
|
||||
component::{Component, Event, EventCtx},
|
||||
display::{self, Color},
|
||||
geometry::{Offset, Rect},
|
||||
model_tt::constant,
|
||||
},
|
||||
};
|
||||
|
||||
@ -32,9 +33,9 @@ pub struct Loader {
|
||||
impl Loader {
|
||||
pub const SIZE: Offset = Offset::new(120, 120);
|
||||
|
||||
pub fn new(offset_y: i32) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
offset_y,
|
||||
offset_y: 0,
|
||||
state: State::Initial,
|
||||
growing_duration: Duration::from_millis(1000),
|
||||
shrinking_duration: Duration::from_millis(500),
|
||||
@ -70,13 +71,16 @@ impl Loader {
|
||||
now,
|
||||
);
|
||||
if let State::Growing(growing) = &self.state {
|
||||
anim.seek_to_value(display::LOADER_MAX - growing.value(now));
|
||||
anim.seek_to_value(display::LOADER_MAX.saturating_sub(growing.value(now)));
|
||||
}
|
||||
self.state = State::Shrinking(anim);
|
||||
|
||||
// The animation should be already progressing at this point, so we don't need
|
||||
// to request another animation frames, but we should request to get painted
|
||||
// after this event pass.
|
||||
// Request anim frame as the animation may not be running, e.g. when already
|
||||
// grown completely.
|
||||
ctx.request_anim_frame();
|
||||
|
||||
// We don't have to wait for the animation frame event with next paint,
|
||||
// let's do that now.
|
||||
ctx.request_paint();
|
||||
}
|
||||
|
||||
@ -112,8 +116,11 @@ impl Component for Loader {
|
||||
type Msg = LoaderMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
// TODO: Return the correct size.
|
||||
bounds
|
||||
// Current loader API only takes Y-offset relative to screen center, which we
|
||||
// compute from the bounds center point.
|
||||
let screen_center = constant::screen().center();
|
||||
self.offset_y = screen_center.y - bounds.center().y;
|
||||
Rect::from_center_and_size(screen_center + Offset::y(self.offset_y), Self::SIZE)
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
@ -188,7 +195,7 @@ mod tests {
|
||||
#[test]
|
||||
fn loader_yields_expected_progress() {
|
||||
let mut ctx = EventCtx::new();
|
||||
let mut l = Loader::new(0);
|
||||
let mut l = Loader::new();
|
||||
let t = Instant::now();
|
||||
assert_eq!(l.progress(t), None);
|
||||
l.start_growing(&mut ctx, t);
|
||||
|
@ -1,7 +1,7 @@
|
||||
mod button;
|
||||
mod confirm;
|
||||
mod dialog;
|
||||
mod frame;
|
||||
mod hold_to_confirm;
|
||||
mod keyboard;
|
||||
mod loader;
|
||||
mod page;
|
||||
@ -9,9 +9,9 @@ mod scroll;
|
||||
mod swipe;
|
||||
|
||||
pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet};
|
||||
pub use confirm::{HoldToConfirm, HoldToConfirmMsg};
|
||||
pub use dialog::{Dialog, DialogLayout, DialogMsg};
|
||||
pub use frame::Frame;
|
||||
pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
|
||||
pub use keyboard::{
|
||||
bip39::Bip39Input,
|
||||
mnemonic::{MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg},
|
||||
@ -20,7 +20,7 @@ pub use keyboard::{
|
||||
slip39::Slip39Input,
|
||||
};
|
||||
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
||||
pub use page::SwipePage;
|
||||
pub use page::{SwipeHoldPage, SwipePage};
|
||||
pub use scroll::ScrollBar;
|
||||
pub use swipe::{Swipe, SwipeDirection};
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
use crate::ui::{
|
||||
component::{
|
||||
base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, Pad, Paginate,
|
||||
base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, Label, Pad, Paginate,
|
||||
},
|
||||
display::{self, Color},
|
||||
geometry::{Offset, Rect},
|
||||
geometry::Rect,
|
||||
};
|
||||
|
||||
use super::{theme, Button, ScrollBar, Swipe, SwipeDirection};
|
||||
use super::{
|
||||
hold_to_confirm::{handle_hold_event, CancelHold, CancelHoldMsg},
|
||||
theme, Button, Loader, ScrollBar, Swipe, SwipeDirection,
|
||||
};
|
||||
|
||||
pub struct SwipePage<T, U> {
|
||||
content: T,
|
||||
@ -14,6 +17,7 @@ pub struct SwipePage<T, U> {
|
||||
pad: Pad,
|
||||
swipe: Swipe,
|
||||
scrollbar: ScrollBar,
|
||||
hint: Label<&'static str>,
|
||||
fade: Option<i32>,
|
||||
}
|
||||
|
||||
@ -30,6 +34,7 @@ where
|
||||
scrollbar: ScrollBar::vertical(),
|
||||
swipe: Swipe::new(),
|
||||
pad: Pad::with_background(background),
|
||||
hint: Label::centered("SWIPE TO CONTINUE", theme::label_page_hint()),
|
||||
fade: None,
|
||||
}
|
||||
}
|
||||
@ -53,16 +58,6 @@ where
|
||||
// paint.
|
||||
self.fade = Some(theme::BACKLIGHT_NORMAL);
|
||||
}
|
||||
|
||||
fn paint_hint(&mut self) {
|
||||
display::text_center(
|
||||
self.pad.area.bottom_center() - Offset::y(3),
|
||||
"SWIPE TO CONTINUE",
|
||||
theme::FONT_BOLD, // FIXME: Figma has this as 14px but bold is 16px
|
||||
theme::GREY_LIGHT,
|
||||
theme::BG,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Component for SwipePage<T, U>
|
||||
@ -77,6 +72,7 @@ where
|
||||
let layout = PageLayout::new(bounds);
|
||||
self.pad.place(bounds);
|
||||
self.swipe.place(bounds);
|
||||
self.hint.place(layout.hint);
|
||||
self.buttons.place(layout.buttons);
|
||||
self.scrollbar.place(layout.scrollbar);
|
||||
|
||||
@ -129,6 +125,8 @@ where
|
||||
if let Some(msg) = self.buttons.event(ctx, event) {
|
||||
return Some(PageMsg::Controls(msg));
|
||||
}
|
||||
} else {
|
||||
self.hint.event(ctx, event);
|
||||
}
|
||||
None
|
||||
}
|
||||
@ -140,7 +138,7 @@ where
|
||||
self.scrollbar.paint();
|
||||
}
|
||||
if self.scrollbar.has_next_page() {
|
||||
self.paint_hint();
|
||||
self.hint.paint();
|
||||
} else {
|
||||
self.buttons.paint();
|
||||
}
|
||||
@ -156,6 +154,8 @@ where
|
||||
self.content.bounds(sink);
|
||||
if !self.scrollbar.has_next_page() {
|
||||
self.buttons.bounds(sink);
|
||||
} else {
|
||||
self.hint.bounds(sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,15 +181,18 @@ pub struct PageLayout {
|
||||
pub content: Rect,
|
||||
pub scrollbar: Rect,
|
||||
pub buttons: Rect,
|
||||
pub hint: Rect,
|
||||
}
|
||||
|
||||
impl PageLayout {
|
||||
const BUTTON_SPACE: i32 = 6;
|
||||
const SCROLLBAR_WIDTH: i32 = 10;
|
||||
const SCROLLBAR_SPACE: i32 = 10;
|
||||
const HINT_OFF: i32 = 19;
|
||||
|
||||
pub fn new(area: Rect) -> Self {
|
||||
let (content, buttons) = area.split_bottom(Button::<&str>::HEIGHT);
|
||||
let (_, hint) = area.split_bottom(Self::HINT_OFF);
|
||||
let (content, _space) = content.split_bottom(Self::BUTTON_SPACE);
|
||||
let (buttons, _space) = buttons.split_right(theme::CONTENT_BORDER);
|
||||
let (_space, content) = content.split_left(theme::CONTENT_BORDER);
|
||||
@ -203,10 +206,103 @@ impl PageLayout {
|
||||
content,
|
||||
scrollbar,
|
||||
buttons,
|
||||
hint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SwipeHoldPage<T> {
|
||||
inner: SwipePage<T, CancelHold>,
|
||||
loader: Loader,
|
||||
}
|
||||
|
||||
impl<T> SwipeHoldPage<T>
|
||||
where
|
||||
T: Paginate,
|
||||
T: Component,
|
||||
{
|
||||
pub fn new(content: T, background: Color) -> Self {
|
||||
let buttons = CancelHold::new();
|
||||
Self {
|
||||
inner: SwipePage::new(content, buttons, background),
|
||||
loader: Loader::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for SwipeHoldPage<T>
|
||||
where
|
||||
T: Paginate,
|
||||
T: Component,
|
||||
{
|
||||
type Msg = PageMsg<T::Msg, bool>;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.inner.place(bounds);
|
||||
self.loader.place(self.inner.pad.area);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let msg = self.inner.event(ctx, event);
|
||||
let button_msg = match msg {
|
||||
Some(PageMsg::Content(c)) => return Some(PageMsg::Content(c)),
|
||||
Some(PageMsg::Controls(CancelHoldMsg::Cancelled)) => {
|
||||
return Some(PageMsg::Controls(false))
|
||||
}
|
||||
Some(PageMsg::Controls(CancelHoldMsg::HoldButton(b))) => Some(b),
|
||||
_ => None,
|
||||
};
|
||||
if handle_hold_event(
|
||||
ctx,
|
||||
event,
|
||||
button_msg,
|
||||
&mut self.loader,
|
||||
&mut self.inner.pad,
|
||||
&mut self.inner.content,
|
||||
) {
|
||||
return Some(PageMsg::Controls(true));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
self.inner.pad.paint();
|
||||
if self.loader.is_animating() {
|
||||
self.loader.paint()
|
||||
} else {
|
||||
self.inner.content.paint();
|
||||
}
|
||||
if self.inner.scrollbar.has_pages() {
|
||||
self.inner.scrollbar.paint();
|
||||
}
|
||||
if self.inner.scrollbar.has_next_page() {
|
||||
self.inner.hint.paint();
|
||||
} else {
|
||||
self.inner.buttons.paint();
|
||||
}
|
||||
if let Some(val) = self.inner.fade.take() {
|
||||
// Note that this is blocking and takes some time.
|
||||
display::fade_backlight(val);
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||
self.loader.bounds(sink);
|
||||
self.inner.bounds(sink);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for SwipeHoldPage<T>
|
||||
where
|
||||
T: crate::trace::Trace,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
self.inner.trace(t)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
|
@ -21,7 +21,7 @@ use super::{
|
||||
component::{
|
||||
Bip39Input, Button, ButtonMsg, Dialog, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg,
|
||||
MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard,
|
||||
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Slip39Input, SwipePage,
|
||||
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Slip39Input, SwipeHoldPage, SwipePage,
|
||||
},
|
||||
theme,
|
||||
};
|
||||
@ -116,6 +116,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
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(true) => Ok(CONFIRMED.as_obj()),
|
||||
PageMsg::Controls(false) => Ok(CANCELLED.as_obj()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
|
@ -87,6 +87,14 @@ pub fn label_keyboard_minor() -> LabelStyle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label_page_hint() -> LabelStyle {
|
||||
LabelStyle {
|
||||
font: FONT_BOLD,
|
||||
text_color: GREY_LIGHT,
|
||||
background_color: BG,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_default() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
@ -285,6 +293,7 @@ impl DefaultTextTheme for TTDefaultText {
|
||||
|
||||
pub const CONTENT_BORDER: i32 = 5;
|
||||
pub const KEYBOARD_SPACING: i32 = 8;
|
||||
pub const BUTTON_SPACING: i32 = 6;
|
||||
|
||||
/// +----------+
|
||||
/// | 13 |
|
||||
|
Loading…
Reference in New Issue
Block a user