1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-21 12:02:19 +00:00

refactor(core/ui): new Mercury design for number input slider

This commit is contained in:
Ioan Bizău 2024-07-15 10:00:05 +02:00 committed by Ioan Bizău
parent 7404436305
commit 7d90552d81
10 changed files with 185 additions and 84 deletions

View File

@ -121,6 +121,8 @@ static void _librust_qstrs(void) {
MP_QSTR_bootscreen;
MP_QSTR_br_code;
MP_QSTR_br_type;
MP_QSTR_brightness__change_title;
MP_QSTR_brightness__changed_title;
MP_QSTR_brightness__title;
MP_QSTR_button;
MP_QSTR_button_event;
@ -295,6 +297,7 @@ static void _librust_qstrs(void) {
MP_QSTR_instructions__learn_more;
MP_QSTR_instructions__shares_continue_with_x_template;
MP_QSTR_instructions__shares_start_with_1;
MP_QSTR_instructions__swipe_horizontally;
MP_QSTR_instructions__swipe_up;
MP_QSTR_instructions__tap_to_confirm;
MP_QSTR_instructions__tap_to_start;
@ -618,6 +621,8 @@ static void _librust_qstrs(void) {
MP_QSTR_send__transaction_signed;
MP_QSTR_send__you_are_contributing;
MP_QSTR_set_brightness;
MP_QSTR_setting__adjust;
MP_QSTR_setting__apply;
MP_QSTR_share_words;
MP_QSTR_share_words__words_in_order;
MP_QSTR_share_words__wrote_down_all;

View File

@ -1330,7 +1330,7 @@ pub enum TranslatedString {
words__settings = 929, // "Settings"
words__try_again = 930, // "Try again."
reset__slip39_checklist_num_groups_x_template = 931, // "Number of groups: {0}"
brightness__title = 932, // "Change display brightness"
brightness__title = 932, // "Display brightness"
recovery__title_unlock_repeated_backup = 933, // "Multi-share backup"
recovery__unlock_repeated_backup = 934, // "Create additional backup?"
recovery__unlock_repeated_backup_verb = 935, // "Create backup"
@ -1349,6 +1349,11 @@ pub enum TranslatedString {
tutorial__title_well_done = 948, // "Well done!"
tutorial__lets_begin = 949, // "Learn how to use and navigate this device with ease."
tutorial__get_started = 950, // "Get started!"
instructions__swipe_horizontally = 951, // "Swipe horizontally"
setting__adjust = 952, // "Adjust"
setting__apply = 953, // "Apply"
brightness__changed_title = 954, // "Display brightness changed"
brightness__change_title = 955, // "Change display brightness"
}
impl TranslatedString {
@ -2673,7 +2678,7 @@ impl TranslatedString {
Self::words__settings => "Settings",
Self::words__try_again => "Try again.",
Self::reset__slip39_checklist_num_groups_x_template => "Number of groups: {0}",
Self::brightness__title => "Change display brightness",
Self::brightness__title => "Display brightness",
Self::recovery__title_unlock_repeated_backup => "Multi-share backup",
Self::recovery__unlock_repeated_backup => "Create additional backup?",
Self::recovery__unlock_repeated_backup_verb => "Create backup",
@ -2692,6 +2697,11 @@ impl TranslatedString {
Self::tutorial__title_well_done => "Well done!",
Self::tutorial__lets_begin => "Learn how to use and navigate this device with ease.",
Self::tutorial__get_started => "Get started!",
Self::instructions__swipe_horizontally => "Swipe horizontally",
Self::setting__adjust => "Adjust",
Self::setting__apply => "Apply",
Self::brightness__changed_title => "Display brightness changed",
Self::brightness__change_title => "Change display brightness",
}
}
@ -4036,6 +4046,11 @@ impl TranslatedString {
Qstr::MP_QSTR_tutorial__title_well_done => Some(Self::tutorial__title_well_done),
Qstr::MP_QSTR_tutorial__lets_begin => Some(Self::tutorial__lets_begin),
Qstr::MP_QSTR_tutorial__get_started => Some(Self::tutorial__get_started),
Qstr::MP_QSTR_instructions__swipe_horizontally => Some(Self::instructions__swipe_horizontally),
Qstr::MP_QSTR_setting__adjust => Some(Self::setting__adjust),
Qstr::MP_QSTR_setting__apply => Some(Self::setting__apply),
Qstr::MP_QSTR_brightness__changed_title => Some(Self::brightness__changed_title),
Qstr::MP_QSTR_brightness__change_title => Some(Self::brightness__change_title),
_ => None,
}
}

View File

@ -20,6 +20,7 @@ mod keyboard;
mod loader;
#[cfg(feature = "translations")]
mod number_input;
#[cfg(feature = "translations")]
pub mod number_input_slider;
mod progress;
#[cfg(feature = "translations")]

View File

@ -1,16 +1,17 @@
use crate::{
strutil::ShortString,
strutil::{ShortString, TString},
translations::TR,
ui::{
component::{base::ComponentExt, Child, Component, Event, EventCtx},
constant::screen,
display,
event::TouchEvent,
geometry::{Alignment, Insets, Point, Rect},
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
shape::{self, Renderer},
},
};
use super::theme;
use super::{theme, Footer};
pub enum NumberInputSliderDialogMsg {
Changed(u16),
@ -18,22 +19,25 @@ pub enum NumberInputSliderDialogMsg {
pub struct NumberInputSliderDialog {
area: Rect,
text_area: Rect,
input: Child<NumberInputSlider>,
footer: Footer<'static>,
min: u16,
max: u16,
val: u16,
init_val: u16,
}
impl NumberInputSliderDialog {
pub fn new(min: u16, max: u16, init_value: u16) -> Self {
Self {
area: Rect::zero(),
text_area: Rect::zero(),
input: NumberInputSlider::new(min, max, init_value).into_child(),
footer: Footer::new::<TString<'static>>(TR::instructions__swipe_horizontally.into())
.with_description::<TString<'static>>(TR::setting__adjust.into()),
min,
max,
val: init_value,
init_val: init_value,
}
}
@ -42,25 +46,50 @@ impl NumberInputSliderDialog {
}
}
const INPUT_AREA_HEIGHT: i16 = 91;
impl Component for NumberInputSliderDialog {
type Msg = NumberInputSliderDialogMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
let content_area = self.area.inset(Insets::top(2 * theme::BUTTON_SPACING));
let (_, content_area) = content_area.split_top(30);
let (input_area, _) = content_area.split_top(15);
let (text_area, _) = content_area.split_bottom(theme::BUTTON_HEIGHT);
self.text_area = text_area;
let whole_area = self.area.inset(Insets::bottom(theme::SPACING));
let (remaining, footer_area) = whole_area.split_bottom(self.footer.height());
self.footer.place(footer_area);
let content_area = remaining;
let used_area = content_area
.inset(Insets::sides(theme::SPACING))
.inset(Insets::bottom(theme::SPACING));
let input_area = Rect::snap(
used_area.center(),
Offset::new(used_area.width(), INPUT_AREA_HEIGHT),
Alignment2D::CENTER,
);
self.input.place(input_area.inset(Insets::sides(20)));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(value) = self.input.event(ctx, event) {
self.val = value;
if self.val == self.init_val || self.input.inner().touching {
self.footer
.update_instruction(ctx, TR::instructions__swipe_horizontally);
self.footer.update_description(ctx, TR::setting__adjust);
ctx.request_paint();
} else {
self.footer
.update_instruction(ctx, TR::instructions__swipe_up);
self.footer.update_description(ctx, TR::setting__apply);
ctx.request_paint();
}
return Some(Self::Msg::Changed(value));
}
None
@ -72,17 +101,7 @@ impl Component for NumberInputSliderDialog {
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.input.render(target);
let mut str = ShortString::new();
let val_pct = (100 * (self.val - self.min)) / (self.max - self.min);
unwrap!(ufmt::uwrite!(str, "{} %", val_pct));
shape::Text::new(self.text_area.center(), &str)
.with_font(theme::TEXT_NORMAL.text_font)
.with_fg(theme::TEXT_NORMAL.text_color)
.with_align(Alignment::Center)
.render(target);
self.footer.render(target);
}
}
@ -97,9 +116,11 @@ impl crate::trace::Trace for NumberInputSliderDialog {
pub struct NumberInputSlider {
area: Rect,
touch_area: Rect,
text_area: Rect,
min: u16,
max: u16,
value: u16,
touching: bool,
}
impl NumberInputSlider {
@ -108,20 +129,27 @@ impl NumberInputSlider {
Self {
area: Rect::zero(),
touch_area: Rect::zero(),
text_area: Rect::zero(),
min,
max,
value,
touching: false,
}
}
pub fn slider_eval(&mut self, pos: Point, ctx: &mut EventCtx) -> Option<u16> {
if self.touch_area.contains(pos) {
pub fn touch_eval(
&mut self,
pos: Point,
ctx: &mut EventCtx,
force_bubble_up: bool,
) -> Option<u16> {
if self.touching {
let filled = pos.x - self.area.x0;
let filled = filled.clamp(0, self.area.width());
let val_pct = (filled as u16 * 100) / self.area.width() as u16;
let val = (val_pct * (self.max - self.min)) / 100 + self.min;
if val != self.value {
if val != self.value || force_bubble_up {
self.value = val;
ctx.request_paint();
return Some(self.value);
@ -136,16 +164,26 @@ impl Component for NumberInputSlider {
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
self.touch_area = bounds.outset(Insets::new(40, 20, 40, 20)).clamp(screen());
self.touch_area = bounds.outset(Insets::new(0, 20, 0, 20)).clamp(screen());
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Touch(touch_event) = event {
return match touch_event {
TouchEvent::TouchStart(pos) => self.slider_eval(pos, ctx),
TouchEvent::TouchMove(pos) => self.slider_eval(pos, ctx),
TouchEvent::TouchEnd(pos) => self.slider_eval(pos, ctx),
TouchEvent::TouchStart(pos) => {
if self.touch_area.contains(pos) {
self.touching = true;
ctx.request_paint();
}
self.touch_eval(pos, ctx, true)
}
TouchEvent::TouchMove(pos) => self.touch_eval(pos, ctx, false),
TouchEvent::TouchEnd(pos) => {
self.touching = false;
ctx.request_paint();
self.touch_eval(pos, ctx, true)
}
TouchEvent::TouchAbort => None,
};
}
@ -160,16 +198,24 @@ impl Component for NumberInputSlider {
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let (top_left_shape, top_right_shape, bot_left_shape, bot_right_shape) =
shape::CornerHighlight::from_rect(
self.area,
if self.touching {
theme::GREY_DARK
} else {
theme::WHITE
},
theme::BG,
);
top_left_shape.render(target);
top_right_shape.render(target);
bot_left_shape.render(target);
bot_right_shape.render(target);
let val_pct = (100 * (self.value - self.min)) / (self.max - self.min);
shape::Bar::new(self.area)
.with_radius(2)
.with_thickness(2)
.with_bg(theme::BG)
.with_fg(theme::FG)
.render(target);
let inner = self.area.inset(Insets::uniform(1));
let inner = self.area.inset(Insets::uniform(10));
let fill_to = (val_pct as i16 * inner.width()) / 100;
@ -177,8 +223,26 @@ impl Component for NumberInputSlider {
shape::Bar::new(inner)
.with_radius(1)
.with_bg(theme::FG)
.with_bg(if self.touching {
theme::WHITE
} else {
theme::GREY_EXTRA_DARK
})
.render(target);
let mut str = ShortString::new();
let val_pct = (100 * (self.value - self.min)) / (self.max - self.min);
unwrap!(ufmt::uwrite!(str, "{} %", val_pct));
if !self.touching {
let text_height = theme::TEXT_BOLD.text_font.line_height();
shape::Text::new(self.area.center() + Offset::new(0, text_height / 2), &str)
.with_font(theme::TEXT_BOLD.text_font)
.with_fg(theme::TEXT_BOLD.text_color)
.with_align(Alignment::Center)
.render(target);
}
}
}

View File

@ -9,8 +9,8 @@ use crate::{
ui::{
component::{base::ComponentExt, swipe_detect::SwipeSettings, SwipeDirection},
flow::{
base::{Decision, FlowMsg},
flow_store, FlowState, FlowStore, SwipeFlow,
base::{DecisionBuilder as _, FlowMsg, StateChange},
FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
model_mercury::component::{
@ -21,7 +21,7 @@ use crate::{
};
use super::super::{
component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg},
component::{Frame, FrameMsg, PromptScreen, StatusScreen, VerticalMenu, VerticalMenuChoiceMsg},
theme,
};
@ -30,41 +30,35 @@ pub enum SetBrightness {
Slider,
Menu,
Confirm,
Confirmed,
}
impl FlowState for SetBrightness {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
#[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) {
(SetBrightness::Menu, SwipeDirection::Right) => {
Decision::Goto(SetBrightness::Slider, direction)
}
(SetBrightness::Slider, SwipeDirection::Up) => {
Decision::Goto(SetBrightness::Confirm, direction)
}
(SetBrightness::Confirm, SwipeDirection::Down) => {
Decision::Goto(SetBrightness::Slider, direction)
}
(SetBrightness::Confirm, SwipeDirection::Left) => {
Decision::Goto(SetBrightness::Menu, direction)
}
_ => Decision::Nothing,
(Self::Menu, SwipeDirection::Right) => Self::Slider.swipe(direction),
(Self::Slider, SwipeDirection::Up) => Self::Confirm.swipe(direction),
(Self::Confirm, SwipeDirection::Down) => Self::Slider.swipe(direction),
(Self::Confirm, SwipeDirection::Left) => Self::Menu.swipe(direction),
(Self::Confirmed, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
_ => self.do_nothing(),
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) {
(SetBrightness::Slider, FlowMsg::Info) => {
Decision::Goto(SetBrightness::Menu, SwipeDirection::Left)
}
(SetBrightness::Menu, FlowMsg::Cancelled) => {
Decision::Goto(SetBrightness::Slider, SwipeDirection::Right)
}
(SetBrightness::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
(SetBrightness::Confirm, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Confirmed),
(SetBrightness::Confirm, FlowMsg::Info) => {
Decision::Goto(SetBrightness::Menu, SwipeDirection::Left)
}
_ => Decision::Nothing,
(Self::Slider, FlowMsg::Info) => Self::Menu.swipe_left(),
(Self::Menu, FlowMsg::Cancelled) => Self::Slider.swipe_right(),
(Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled),
(Self::Confirm, FlowMsg::Confirmed) => Self::Confirmed.swipe_up(),
(Self::Confirm, FlowMsg::Info) => Self::Menu.swipe_left(),
(Self::Confirmed, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
_ => self.do_nothing(),
}
}
}
@ -87,8 +81,8 @@ impl SetBrightness {
current.unwrap_or(theme::backlight::get_backlight_normal()),
),
)
.with_subtitle(TR::homescreen__settings_subtitle.into())
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
.map(|msg| match msg {
FrameMsg::Content(NumberInputSliderDialogMsg::Changed(n)) => {
@ -111,7 +105,7 @@ impl SetBrightness {
});
let content_confirm = Frame::left_aligned(
TR::brightness__title.into(),
TR::brightness__change_title.into(),
SwipeContent::new(PromptScreen::new_tap_to_confirm()),
)
.with_footer(TR::instructions__tap_to_confirm.into(), None)
@ -126,12 +120,19 @@ impl SetBrightness {
FrameMsg::Button(_) => Some(FlowMsg::Info),
});
let store = flow_store()
.add(content_slider)?
.add(content_menu)?
.add(content_confirm)?;
let content_confirmed = Frame::left_aligned(
TR::brightness__changed_title.into(),
SwipeContent::new(StatusScreen::new_success()).with_no_attach_anim(),
)
.with_footer(TR::instructions__swipe_up.into(), None)
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
.map(move |_msg| Some(FlowMsg::Confirmed));
let res = SwipeFlow::new(SetBrightness::Slider, store)?;
let res = SwipeFlow::new(&SetBrightness::Slider)?
.with_page(&SetBrightness::Slider, content_slider)?
.with_page(&SetBrightness::Menu, content_menu)?
.with_page(&SetBrightness::Confirm, content_confirm)?
.with_page(&SetBrightness::Confirmed, content_confirmed)?;
Ok(LayoutObj::new(res)?.into())
}

View File

@ -6,8 +6,8 @@ use super::{
CoinJoinProgress, FidoConfirm, FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg,
Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard,
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, PromptScreen,
SelectWordCount, SelectWordCountMsg, SetBrightnessDialog, Slip39Input, StatusScreen,
SwipeUpScreen, SwipeUpScreenMsg, VerticalMenu, VerticalMenuChoiceMsg,
SelectWordCount, SelectWordCountMsg, Slip39Input, StatusScreen, SwipeUpScreen,
SwipeUpScreenMsg, VerticalMenu, VerticalMenuChoiceMsg,
},
flow, theme,
};

View File

@ -79,7 +79,9 @@ class TR:
bitcoin__unverified_external_inputs: str = "The transaction contains unverified external inputs."
bitcoin__valid_signature: str = "The signature is valid."
bitcoin__voting_rights: str = "Voting rights to:"
brightness__title: str = "Change display brightness"
brightness__change_title: str = "Change display brightness"
brightness__changed_title: str = "Display brightness changed"
brightness__title: str = "Display brightness"
buttons__abort: str = "Abort"
buttons__access: str = "Access"
buttons__again: str = "Again"
@ -385,6 +387,7 @@ class TR:
instructions__learn_more: str = "Learn more"
instructions__shares_continue_with_x_template: str = "Continue with Share #{0}"
instructions__shares_start_with_1: str = "Start with share #1"
instructions__swipe_horizontally: str = "Swipe horizontally"
instructions__swipe_up: str = "Swipe up"
instructions__tap_to_confirm: str = "Tap to confirm"
instructions__tap_to_start: str = "Tap to start"
@ -742,6 +745,8 @@ class TR:
send__transaction_id: str = "Transaction ID:"
send__transaction_signed: str = "Transaction signed"
send__you_are_contributing: str = "You are contributing:"
setting__adjust: str = "Adjust"
setting__apply: str = "Apply"
share_words__words_in_order: str = " words in order."
share_words__wrote_down_all: str = "I wrote down all "
sign_message__bytes_template: str = "{0} Bytes"

View File

@ -81,7 +81,9 @@
"bitcoin__unverified_external_inputs": "The transaction contains unverified external inputs.",
"bitcoin__valid_signature": "The signature is valid.",
"bitcoin__voting_rights": "Voting rights to:",
"brightness__title": "Change display brightness",
"brightness__title": "Display brightness",
"brightness__change_title": "Change display brightness",
"brightness__changed_title": "Display brightness changed",
"buttons__abort": "Abort",
"buttons__access": "Access",
"buttons__again": "Again",
@ -388,6 +390,7 @@
"instructions__shares_continue_with_x_template": "Continue with Share #{0}",
"instructions__shares_start_with_1": "Start with share #1",
"instructions__swipe_up": "Swipe up",
"instructions__swipe_horizontally": "Swipe horizontally",
"instructions__tap_to_confirm": "Tap to confirm",
"instructions__tap_to_start": "Tap to start",
"joint__title": "Joint transaction",
@ -744,6 +747,8 @@
"send__transaction_id": "Transaction ID:",
"send__transaction_signed": "Transaction signed",
"send__you_are_contributing": "You are contributing:",
"setting__adjust": "Adjust",
"setting__apply": "Apply",
"share_words__words_in_order": " words in order.",
"share_words__wrote_down_all": "I wrote down all ",
"sign_message__bytes_template": "{0} Bytes",

View File

@ -949,5 +949,10 @@
"947": "tutorial__title_hold",
"948": "tutorial__title_well_done",
"949": "tutorial__lets_begin",
"950": "tutorial__get_started"
"950": "tutorial__get_started",
"951": "instructions__swipe_horizontally",
"952": "setting__adjust",
"953": "setting__apply",
"954": "brightness__changed_title",
"955": "brightness__change_title"
}

View File

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "f8fe7da5605cc8f5136d8d77b526df530daddbb6795e7c7fa1906822d3dd2a10",
"datetime": "2024-07-08T22:50:20.632650",
"commit": "c9c4bf63623d411384737662ec2aefc88824812d"
"merkle_root": "c94efabb9554eea2adb7f312469fa8b366503bbdb57dd7f92fa67b23dc63bb97",
"datetime": "2024-07-15T09:41:37.793874",
"commit": "5935708ac86d336364e5ab78fc5fd67b19402eaf"
},
"history": [
{