mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-25 07:58:12 +00:00
chore(core/rust): improve the design of TR's tutorial flow
[no changelog]
This commit is contained in:
parent
947e2ee24f
commit
13cb1ea4ce
@ -101,6 +101,11 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
|
|||||||
// Try to fit text on the current page and if they do not fit,
|
// Try to fit text on the current page and if they do not fit,
|
||||||
// return the appropriate OutOfBounds message
|
// return the appropriate OutOfBounds message
|
||||||
|
|
||||||
|
// Inserting the ellipsis at the very beginning of the text if needed
|
||||||
|
// (just once for the first Op::Text on the non-first page).
|
||||||
|
self.layout.continues_from_prev_page =
|
||||||
|
skip_bytes > 0 && total_processed_chars == 0;
|
||||||
|
|
||||||
let fit = self.layout.layout_text(text.as_ref(), cursor, sink);
|
let fit = self.layout.layout_text(text.as_ref(), cursor, sink);
|
||||||
|
|
||||||
match fit {
|
match fit {
|
||||||
|
@ -29,6 +29,7 @@ where
|
|||||||
buttons: Child<ButtonController<T>>,
|
buttons: Child<ButtonController<T>>,
|
||||||
page_counter: usize,
|
page_counter: usize,
|
||||||
return_confirmed_index: bool,
|
return_confirmed_index: bool,
|
||||||
|
show_scrollbar: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T> Flow<F, T>
|
impl<F, T> Flow<F, T>
|
||||||
@ -52,6 +53,7 @@ where
|
|||||||
buttons: Child::new(ButtonController::new(ButtonLayout::empty())),
|
buttons: Child::new(ButtonController::new(ButtonLayout::empty())),
|
||||||
page_counter: 0,
|
page_counter: 0,
|
||||||
return_confirmed_index: false,
|
return_confirmed_index: false,
|
||||||
|
show_scrollbar: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +70,12 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show scrollbar or not.
|
||||||
|
pub fn with_scrollbar(mut self, show_scrollbar: bool) -> Self {
|
||||||
|
self.show_scrollbar = show_scrollbar;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn confirmed_index(&self) -> Option<usize> {
|
pub fn confirmed_index(&self) -> Option<usize> {
|
||||||
self.return_confirmed_index.then_some(self.page_counter)
|
self.return_confirmed_index.then_some(self.page_counter)
|
||||||
}
|
}
|
||||||
@ -203,8 +211,11 @@ where
|
|||||||
// (scrollbar will be active - counting pages - even when not placed and
|
// (scrollbar will be active - counting pages - even when not placed and
|
||||||
// painted)
|
// painted)
|
||||||
if self.title.is_some() {
|
if self.title.is_some() {
|
||||||
let (title_area, scrollbar_area) =
|
let (title_area, scrollbar_area) = if self.show_scrollbar {
|
||||||
title_area.split_right(self.scrollbar.inner().overall_width() + SCROLLBAR_SPACE);
|
title_area.split_right(self.scrollbar.inner().overall_width() + SCROLLBAR_SPACE)
|
||||||
|
} else {
|
||||||
|
(title_area, Rect::zero())
|
||||||
|
};
|
||||||
|
|
||||||
self.title.place(title_area);
|
self.title.place(title_area);
|
||||||
self.title_area = title_area;
|
self.title_area = title_area;
|
||||||
@ -265,9 +276,11 @@ where
|
|||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
self.pad.paint();
|
self.pad.paint();
|
||||||
// Scrollbars are painted only with a title
|
// Scrollbars are painted only with a title and when requested
|
||||||
if self.title.is_some() {
|
if self.title.is_some() {
|
||||||
self.scrollbar.paint();
|
if self.show_scrollbar {
|
||||||
|
self.scrollbar.paint();
|
||||||
|
}
|
||||||
self.title.paint();
|
self.title.paint();
|
||||||
}
|
}
|
||||||
self.buttons.paint();
|
self.buttons.paint();
|
||||||
|
@ -31,8 +31,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
ComponentExt, FormattedText, LineBreaking, Timeout,
|
ComponentExt, FormattedText, LineBreaking, Timeout,
|
||||||
},
|
},
|
||||||
display::{self},
|
display, geometry,
|
||||||
geometry::Alignment,
|
|
||||||
layout::{
|
layout::{
|
||||||
obj::{ComponentMsgObj, LayoutObj},
|
obj::{ComponentMsgObj, LayoutObj},
|
||||||
result::{CANCELLED, CONFIRMED, INFO},
|
result::{CANCELLED, CONFIRMED, INFO},
|
||||||
@ -641,22 +640,16 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// General pattern of most tutorial screens.
|
/// General pattern of most tutorial screens.
|
||||||
/// (title, text, btn_layout, btn_actions)
|
/// (title, text, btn_layout, btn_actions, text_y_offset)
|
||||||
fn tutorial_screen(
|
fn tutorial_screen(
|
||||||
title: &'static str,
|
title: &'static str,
|
||||||
text: &'static str,
|
text: &'static str,
|
||||||
btn_layout: ButtonLayout<StrBuffer>,
|
btn_layout: ButtonLayout<StrBuffer>,
|
||||||
btn_actions: ButtonActions,
|
btn_actions: ButtonActions,
|
||||||
) -> Page<StrBuffer> {
|
) -> Page<StrBuffer> {
|
||||||
let mut ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL);
|
let ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL).text_normal(text.into());
|
||||||
// Add title if present
|
let formatted = FormattedText::new(ops).vertically_aligned(geometry::Alignment::Center);
|
||||||
if !title.is_empty() {
|
Page::new(btn_layout, btn_actions, formatted).with_title(title.into())
|
||||||
ops = ops.text_bold(title.into()).newline().newline_half()
|
|
||||||
}
|
|
||||||
ops = ops.text_normal(text.into());
|
|
||||||
|
|
||||||
let formatted = FormattedText::new(ops);
|
|
||||||
Page::new(btn_layout, btn_actions, formatted)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
@ -674,15 +667,15 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
|||||||
0 => {
|
0 => {
|
||||||
tutorial_screen(
|
tutorial_screen(
|
||||||
"HELLO",
|
"HELLO",
|
||||||
"Welcome to Trezor.\nPress right to continue.",
|
"Welcome to Trezor. Press right to continue.",
|
||||||
ButtonLayout::text_none_arrow("SKIP".into()),
|
ButtonLayout::cancel_none_arrow(),
|
||||||
ButtonActions::last_none_next(),
|
ButtonActions::last_none_next(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
1 => {
|
1 => {
|
||||||
tutorial_screen(
|
tutorial_screen(
|
||||||
"",
|
"",
|
||||||
"Use Trezor by clicking left and right buttons.\n\nContinue right.",
|
"Use Trezor by\nclicking the left and right buttons.\n\rContinue right.",
|
||||||
ButtonLayout::arrow_none_arrow(),
|
ButtonLayout::arrow_none_arrow(),
|
||||||
ButtonActions::prev_none_next(),
|
ButtonActions::prev_none_next(),
|
||||||
)
|
)
|
||||||
@ -690,7 +683,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
|||||||
2 => {
|
2 => {
|
||||||
tutorial_screen(
|
tutorial_screen(
|
||||||
"HOLD TO CONFIRM",
|
"HOLD TO CONFIRM",
|
||||||
"Press and hold right to approve important operations.",
|
"Press and hold the right button to\napprove important operations.",
|
||||||
ButtonLayout::arrow_none_htc("HOLD TO CONFIRM".into()),
|
ButtonLayout::arrow_none_htc("HOLD TO CONFIRM".into()),
|
||||||
ButtonActions::prev_none_next(),
|
ButtonActions::prev_none_next(),
|
||||||
)
|
)
|
||||||
@ -698,7 +691,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
|||||||
3 => {
|
3 => {
|
||||||
tutorial_screen(
|
tutorial_screen(
|
||||||
"SCREEN SCROLL",
|
"SCREEN SCROLL",
|
||||||
"Press right to scroll down to read all content when text\ndoesn't fit on one screen. Press left to scroll up.",
|
"Press right to scroll down to read all content when text doesn't fit on one screen.\n\rPress left to scroll up.",
|
||||||
ButtonLayout::arrow_none_text("CONTINUE".into()),
|
ButtonLayout::arrow_none_text("CONTINUE".into()),
|
||||||
ButtonActions::prev_none_next(),
|
ButtonActions::prev_none_next(),
|
||||||
)
|
)
|
||||||
@ -706,33 +699,24 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
|||||||
4 => {
|
4 => {
|
||||||
tutorial_screen(
|
tutorial_screen(
|
||||||
"CONFIRM",
|
"CONFIRM",
|
||||||
"Press both left and right at the same time to confirm.",
|
"Press both left and right at the same\ntime to confirm.",
|
||||||
ButtonLayout::none_armed_none("CONFIRM".into()),
|
ButtonLayout::none_armed_none("CONFIRM".into()),
|
||||||
ButtonActions::prev_next_none(),
|
ButtonActions::prev_next_none(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
// This page is special
|
|
||||||
5 => {
|
5 => {
|
||||||
let ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL)
|
tutorial_screen(
|
||||||
.newline()
|
"TUTORIAL COMPLETE",
|
||||||
.text_normal("Tutorial complete.".into())
|
"You're ready to\nuse Trezor.",
|
||||||
.newline()
|
ButtonLayout::text_none_text("AGAIN".into(), "CONTINUE".into()),
|
||||||
.newline()
|
|
||||||
.alignment(Alignment::Center)
|
|
||||||
.text_bold("You're ready to\nuse Trezor.".into());
|
|
||||||
let formatted = FormattedText::new(ops);
|
|
||||||
|
|
||||||
Page::new(
|
|
||||||
ButtonLayout::text_none_text("AGAIN".into(), "FINISH".into()),
|
|
||||||
ButtonActions::beginning_none_confirm(),
|
ButtonActions::beginning_none_confirm(),
|
||||||
formatted,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
6 => {
|
6 => {
|
||||||
tutorial_screen(
|
tutorial_screen(
|
||||||
"SKIP TUTORIAL",
|
"SKIP TUTORIAL",
|
||||||
"Are you sure you want to skip the tutorial?",
|
"Are you sure you\nwant to skip the tutorial?",
|
||||||
ButtonLayout::cancel_none_text("SKIP".into()),
|
ButtonLayout::arrow_none_text("SKIP".into()),
|
||||||
ButtonActions::beginning_none_cancel(),
|
ButtonActions::beginning_none_cancel(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -742,7 +726,11 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
|||||||
|
|
||||||
let pages = FlowPages::new(get_page, PAGE_COUNT);
|
let pages = FlowPages::new(get_page, PAGE_COUNT);
|
||||||
|
|
||||||
let obj = LayoutObj::new(Flow::new(pages))?;
|
let obj = LayoutObj::new(
|
||||||
|
Flow::new(pages)
|
||||||
|
.with_scrollbar(false)
|
||||||
|
.with_common_title("HELLO".into()),
|
||||||
|
)?;
|
||||||
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) }
|
||||||
|
@ -49,7 +49,7 @@ def go_through_tutorial(debug: "DebugLink") -> None:
|
|||||||
debug.press_right(wait=True)
|
debug.press_right(wait=True)
|
||||||
debug.press_right(wait=True)
|
debug.press_right(wait=True)
|
||||||
layout = debug.press_middle(wait=True)
|
layout = debug.press_middle(wait=True)
|
||||||
assert "Tutorial complete" in layout.text_content()
|
assert layout.title() == "TUTORIAL COMPLETE"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
@ -66,8 +66,6 @@ def test_tutorial_finish(device_handler: "BackgroundDeviceHandler"):
|
|||||||
def test_tutorial_skip(device_handler: "BackgroundDeviceHandler"):
|
def test_tutorial_skip(device_handler: "BackgroundDeviceHandler"):
|
||||||
with prepare_tutorial_and_cancel_after_it(device_handler) as debug:
|
with prepare_tutorial_and_cancel_after_it(device_handler) as debug:
|
||||||
# SKIP
|
# SKIP
|
||||||
# debug.press_left()
|
|
||||||
# debug.press_right()
|
|
||||||
debug.press_left(wait=True)
|
debug.press_left(wait=True)
|
||||||
debug.press_right(wait=True)
|
debug.press_right(wait=True)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user