fix(core/mercury): change status screen - display message in center after animation

Because some messages didn't fit the title
pull/4029/head
tychovrahe 2 months ago committed by TychoVrahe
parent 747303fd39
commit f2bdd6e189

@ -0,0 +1 @@
[T3T1] Fixed title sometimes not fitting into result screen

@ -771,6 +771,7 @@ static void _librust_qstrs(void) {
MP_QSTR_words__sign;
MP_QSTR_words__signer;
MP_QSTR_words__title_check;
MP_QSTR_words__title_done;
MP_QSTR_words__title_group;
MP_QSTR_words__title_information;
MP_QSTR_words__title_remember;

@ -1354,6 +1354,7 @@ pub enum TranslatedString {
setting__apply = 953, // "Apply"
brightness__changed_title = 954, // "Display brightness changed"
brightness__change_title = 955, // "Change display brightness"
words__title_done = 956, // "Done"
}
impl TranslatedString {
@ -2702,6 +2703,7 @@ impl TranslatedString {
Self::setting__apply => "Apply",
Self::brightness__changed_title => "Display brightness changed",
Self::brightness__change_title => "Change display brightness",
Self::words__title_done => "Done",
}
}
@ -4051,6 +4053,7 @@ impl TranslatedString {
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),
Qstr::MP_QSTR_words__title_done => Some(Self::words__title_done),
_ => None,
}
}

@ -1,3 +1,4 @@
use super::{theme, ButtonMsg, ButtonStyleSheet, CancelInfoConfirmMsg, Footer, Header};
use crate::{
strutil::TString,
ui::{
@ -8,7 +9,7 @@ use crate::{
Event::Swipe,
EventCtx, SwipeDetect, SwipeDirection,
},
display::Icon,
display::{Color, Icon},
event::SwipeEvent,
geometry::{Alignment, Insets, Point, Rect},
lerp::Lerp,
@ -18,8 +19,6 @@ use crate::{
},
};
use super::{theme, ButtonMsg, ButtonStyleSheet, CancelInfoConfirmMsg, Footer, Header};
#[derive(Clone)]
pub struct HorizontalSwipe {
progress: i16,
@ -179,6 +178,11 @@ where
self
}
pub fn with_result_icon(mut self, icon: Icon, color: Color) -> Self {
self.header = self.header.with_result_icon(icon, color);
self
}
#[inline(never)]
pub fn with_footer(
mut self,

@ -1,18 +1,65 @@
use crate::{
strutil::TString,
time::{Duration, Stopwatch},
ui::{
component::{text::TextStyle, Component, Event, EventCtx, Label},
display::Icon,
geometry::{Alignment, Insets, Rect},
display::{Color, Icon},
geometry::{Alignment, Alignment2D, Insets, Offset, Rect},
lerp::Lerp,
model_mercury::{
component::{Button, ButtonMsg, ButtonStyleSheet},
theme,
theme::TITLE_HEIGHT,
},
shape,
shape::Renderer,
util::animation_disabled,
},
};
const ANIMATION_TIME_MS: u32 = 1000;
#[derive(Default, Clone)]
struct AttachAnimation {
pub timer: Stopwatch,
}
impl AttachAnimation {
pub fn is_active(&self) -> bool {
if animation_disabled() {
return false;
}
self.timer
.is_running_within(Duration::from_millis(ANIMATION_TIME_MS))
}
pub fn eval(&self) -> f32 {
if animation_disabled() {
return ANIMATION_TIME_MS as f32 / 1000.0;
}
self.timer.elapsed().to_millis() as f32 / 1000.0
}
pub fn get_title_offset(&self, t: f32) -> i16 {
let fnc = pareen::constant(0.0).seq_ease_in_out(
0.8,
easer::functions::Cubic,
0.2,
pareen::constant(1.0),
);
i16::lerp(0, 25, fnc.eval(t))
}
pub fn start(&mut self) {
self.timer.start();
}
pub fn reset(&mut self) {
self.timer = Stopwatch::new_stopped();
}
}
const BUTTON_EXPAND_BORDER: i16 = 32;
#[derive(Clone)]
pub struct Header {
@ -20,6 +67,10 @@ pub struct Header {
title: Label<'static>,
subtitle: Option<Label<'static>>,
button: Option<Button>,
anim: Option<AttachAnimation>,
icon: Option<Icon>,
color: Option<Color>,
title_style: TextStyle,
}
impl Header {
@ -29,32 +80,37 @@ impl Header {
title: Label::new(title, alignment, theme::label_title_main()).vertically_centered(),
subtitle: None,
button: None,
anim: None,
icon: None,
color: None,
title_style: theme::label_title_main(),
}
}
#[inline(never)]
pub fn with_subtitle(mut self, subtitle: TString<'static>) -> Self {
let style = theme::TEXT_SUB_GREY;
self.title = self.title.top_aligned();
self.subtitle = Some(Label::new(subtitle, self.title.alignment(), style));
self
}
#[inline(never)]
pub fn styled(mut self, style: TextStyle) -> Self {
self.title_style = style;
self.title = self.title.styled(style);
self
}
#[inline(never)]
pub fn subtitle_styled(mut self, style: TextStyle) -> Self {
if let Some(subtitle) = self.subtitle.take() {
self.subtitle = Some(subtitle.styled(style))
}
self
}
#[inline(never)]
pub fn update_title(&mut self, title: TString<'static>) {
self.title.set_text(title);
}
#[inline(never)]
pub fn update_subtitle(
&mut self,
new_subtitle: TString<'static>,
@ -72,6 +128,7 @@ impl Header {
}
}
#[inline(never)]
pub fn with_button(mut self, icon: Icon, enabled: bool) -> Self {
let touch_area = Insets::uniform(BUTTON_EXPAND_BORDER);
self.button = Some(
@ -82,6 +139,7 @@ impl Header {
);
self
}
#[inline(never)]
pub fn button_styled(mut self, style: ButtonStyleSheet) -> Self {
if self.button.is_some() {
@ -89,6 +147,16 @@ impl Header {
}
self
}
#[inline(never)]
pub fn with_result_icon(mut self, icon: Icon, color: Color) -> Self {
self.anim = Some(AttachAnimation::default());
self.icon = Some(icon);
self.color = Some(color);
let mut title_style = self.title_style;
title_style.text_color = color;
self.styled(title_style)
}
}
impl Component for Header {
@ -111,6 +179,7 @@ impl Component for Header {
self.title.place(header_area);
}
self.area = bounds;
bounds
}
@ -118,6 +187,20 @@ impl Component for Header {
self.title.event(ctx, event);
self.subtitle.event(ctx, event);
if let Some(anim) = &mut self.anim {
if let Event::Attach(_) = event {
anim.start();
ctx.request_paint();
ctx.request_anim_frame();
}
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
if anim.is_active() {
ctx.request_anim_frame();
ctx.request_paint();
}
}
}
self.button.event(ctx, event)
}
@ -126,9 +209,28 @@ impl Component for Header {
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let offset = if let Some(anim) = &self.anim {
Offset::x(anim.get_title_offset(anim.eval()))
} else {
Offset::zero()
};
self.button.render(target);
self.title.render(target);
self.subtitle.render(target);
target.in_clip(self.area.split_left(offset.x).0, &|target| {
if let Some(icon) = self.icon {
let color = self.color.unwrap_or(theme::GREEN);
shape::ToifImage::new(self.title.area().left_center(), icon.toif)
.with_fg(color)
.with_align(Alignment2D::CENTER_LEFT)
.render(target);
}
});
target.with_origin(offset, &|target| {
self.title.render(target);
self.subtitle.render(target);
});
}
}

@ -1,10 +1,12 @@
use super::theme;
use crate::{
strutil::TString,
time::{Duration, Stopwatch},
ui::{
component::{Component, Event, EventCtx, Timeout},
component::{Component, Event, EventCtx, Label, Timeout},
constant::screen,
display::{Color, Icon},
geometry::{Alignment2D, Insets, Rect},
geometry::{Alignment, Alignment2D, Insets, Point, Rect},
lerp::Lerp,
shape,
shape::Renderer,
@ -12,9 +14,8 @@ use crate::{
},
};
use super::theme;
const TIMEOUT_MS: u32 = 2000;
const ANIMATION_TIME_MS: u32 = 1200;
const TIMEOUT_MS: u32 = ANIMATION_TIME_MS + 2000;
#[derive(Default, Clone)]
struct StatusAnimation {
@ -28,12 +29,12 @@ impl StatusAnimation {
}
self.timer
.is_running_within(Duration::from_millis(TIMEOUT_MS))
.is_running_within(Duration::from_millis(ANIMATION_TIME_MS))
}
pub fn eval(&self) -> f32 {
if animation_disabled() {
return TIMEOUT_MS as f32 / 1000.0;
return ANIMATION_TIME_MS as f32 / 1000.0;
}
self.timer.elapsed().to_millis() as f32 / 1000.0
}
@ -67,14 +68,54 @@ impl StatusAnimation {
pareen::constant(1.0),
);
let circle_scale2 = pareen::constant(0.0).seq_ease_out(
0.8,
easer::functions::Linear,
0.2,
pareen::constant(1.0),
);
const CIRCLE_DIAMETER_MAX: i16 = 170;
const CIRCLE_DIAMETER_MIN: i16 = 80;
i16::lerp(
let a = i16::lerp(
CIRCLE_DIAMETER_MAX / 2,
CIRCLE_DIAMETER_MIN / 2,
circle_scale.eval(t),
)
);
let b = i16::lerp(CIRCLE_DIAMETER_MIN / 2, 0, circle_scale2.eval(t));
if t > 0.8 {
b
} else {
a
}
}
pub fn get_circle_position(&self, t: f32, start: Point) -> Point {
let circle_pos = pareen::constant(0.0).seq_ease_in_out(
0.8,
easer::functions::Linear,
0.2,
pareen::constant(1.0),
);
const CIRCLE_DIAMETER_MAX: i16 = 170;
const CIRCLE_DIAMETER_MIN: i16 = 80;
Point::lerp(start, Point::zero(), circle_pos.eval(t))
}
pub fn get_content2_opacity(&self, t: f32) -> u8 {
let content_opacity = pareen::constant(0.0).seq_ease_in(
1.0,
easer::functions::Linear,
0.2,
pareen::constant(1.0),
);
u8::lerp(0, 255, content_opacity.eval(t))
}
pub fn start(&mut self) {
@ -96,6 +137,7 @@ pub struct StatusScreen {
circle_color: Color,
dismiss_type: DismissType,
anim: StatusAnimation,
msg: Label<'static>,
}
#[derive(Clone)]
@ -105,7 +147,13 @@ enum DismissType {
}
impl StatusScreen {
fn new(icon: Icon, icon_color: Color, circle_color: Color, dismiss_style: DismissType) -> Self {
fn new(
icon: Icon,
icon_color: Color,
circle_color: Color,
dismiss_style: DismissType,
msg: TString<'static>,
) -> Self {
Self {
area: Rect::zero(),
icon,
@ -113,42 +161,47 @@ impl StatusScreen {
circle_color,
dismiss_type: dismiss_style,
anim: StatusAnimation::default(),
msg: Label::new(msg, Alignment::Start, theme::TEXT_NORMAL).vertically_centered(),
}
}
pub fn new_success() -> Self {
pub fn new_success(msg: TString<'static>) -> Self {
Self::new(
theme::ICON_SIMPLE_CHECKMARK,
theme::GREEN_LIME,
theme::GREEN_LIGHT,
DismissType::SwipeUp,
msg,
)
}
pub fn new_success_timeout() -> Self {
pub fn new_success_timeout(msg: TString<'static>) -> Self {
Self::new(
theme::ICON_SIMPLE_CHECKMARK,
theme::GREEN_LIME,
theme::GREEN_LIGHT,
DismissType::Timeout(Timeout::new(TIMEOUT_MS)),
msg,
)
}
pub fn new_neutral() -> Self {
pub fn new_neutral(msg: TString<'static>) -> Self {
Self::new(
theme::ICON_SIMPLE_CHECKMARK,
theme::GREY_EXTRA_LIGHT,
theme::GREY_DARK,
DismissType::SwipeUp,
msg,
)
}
pub fn new_neutral_timeout() -> Self {
pub fn new_neutral_timeout(msg: TString<'static>) -> Self {
Self::new(
theme::ICON_SIMPLE_CHECKMARK,
theme::GREY_EXTRA_LIGHT,
theme::GREY_DARK,
DismissType::Timeout(Timeout::new(TIMEOUT_MS)),
msg,
)
}
}
@ -158,6 +211,7 @@ impl Component for StatusScreen {
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
self.msg.place(bounds);
bounds
}
@ -190,15 +244,25 @@ impl Component for StatusScreen {
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let t = self.anim.eval();
shape::Circle::new(self.area.center(), self.anim.get_circle_radius(t))
let pos = self.anim.get_circle_position(t, self.area.center());
let content_2_alpha = self.anim.get_content2_opacity(t);
let content_1_alpha = 255 - content_2_alpha;
shape::Circle::new(pos, self.anim.get_circle_radius(t))
.with_fg(self.circle_color)
.with_bg(theme::BLACK)
.with_thickness(2)
.with_alpha(content_1_alpha)
.render(target);
shape::ToifImage::new(self.area.center(), self.icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(self.icon_color)
.render(target);
if self.anim.get_circle_radius(t) > 20 {
shape::ToifImage::new(pos, self.icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(self.icon_color)
.with_alpha(content_1_alpha)
.render(target);
}
//content + header cover
shape::Bar::new(self.area.outset(Insets::top(self.area.y0)))
@ -213,6 +277,8 @@ impl Component for StatusScreen {
.with_bg(theme::BLACK)
.with_alpha(255 - self.anim.get_instruction_opacity(t))
.render(target);
self.msg.render_with_alpha(target, content_2_alpha);
}
}
@ -220,5 +286,6 @@ impl Component for StatusScreen {
impl crate::trace::Trace for StatusScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("StatusScreen");
t.child("msg", &self.msg);
}
}

@ -148,10 +148,11 @@ impl GetAddress {
});
let content_confirmed = Frame::left_aligned(
TR::address__confirmed.into(),
StatusScreen::new_success_timeout(),
TR::words__title_success.into(),
StatusScreen::new_success_timeout(TR::address__confirmed.into()),
)
.with_footer(TR::instructions__continue_in_app.into(), None)
.with_result_icon(theme::ICON_BULLET_CHECKMARK, theme::GREEN_LIGHT)
.map(|_| Some(FlowMsg::Confirmed));
// Menu

@ -121,8 +121,11 @@ impl SetBrightness {
});
let content_confirmed = Frame::left_aligned(
TR::brightness__changed_title.into(),
SwipeContent::new(StatusScreen::new_success()).with_no_attach_anim(),
TR::words__title_success.into(),
SwipeContent::new(StatusScreen::new_success(
TR::brightness__changed_title.into(),
))
.with_no_attach_anim(),
)
.with_footer(TR::instructions__swipe_up.into(), None)
.with_swipe(SwipeDirection::Up, SwipeSettings::default())

@ -104,10 +104,13 @@ impl WarningHiPrio {
});
// Cancelled
let content_cancelled =
Frame::left_aligned(done_title, StatusScreen::new_neutral_timeout())
.with_footer(TR::instructions__continue_in_app.into(), None)
.map(|_| Some(FlowMsg::Cancelled));
let content_cancelled = Frame::left_aligned(
TR::words__title_done.into(),
StatusScreen::new_neutral_timeout(done_title),
)
.with_footer(TR::instructions__continue_in_app.into(), None)
.with_result_icon(theme::ICON_BULLET_CHECKMARK, theme::GREY_DARK)
.map(|_| Some(FlowMsg::Cancelled));
let res = SwipeFlow::new(&WarningHiPrio::Message)?
.with_page(&WarningHiPrio::Message, content_message)?

@ -54,6 +54,7 @@ use crate::{
model_mercury::{
component::{check_homescreen_format, SwipeContent},
flow::new_confirm_action_simple,
theme::ICON_BULLET_CHECKMARK,
},
},
};
@ -799,11 +800,15 @@ extern "C" fn new_show_success(n_args: usize, args: *const Obj, kwargs: *mut Map
.try_into_option()?
.and_then(|desc: TString| if desc.is_empty() { None } else { Some(desc) });
let content = StatusScreen::new_success();
let content = StatusScreen::new_success(title);
let obj = LayoutObj::new(SwipeUpScreen::new(
Frame::left_aligned(title, SwipeContent::new(content).with_no_attach_anim())
.with_footer(TR::instructions__swipe_up.into(), description)
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
Frame::left_aligned(
TR::words__title_success.into(),
SwipeContent::new(content).with_no_attach_anim(),
)
.with_footer(TR::instructions__swipe_up.into(), description)
.with_result_icon(ICON_BULLET_CHECKMARK, theme::GREEN_LIGHT)
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
))?;
Ok(obj.into())
};

@ -938,6 +938,7 @@ class TR:
words__sign: str = "Sign"
words__signer: str = "Signer"
words__title_check: str = "Check"
words__title_done: str = "Done"
words__title_group: str = "Group"
words__title_information: str = "Information"
words__title_remember: str = "Remember"

@ -946,6 +946,7 @@
"words__title_share": "Share",
"words__title_shares": "Shares",
"words__title_success": "Success",
"words__title_done": "Done",
"words__title_summary": "Summary",
"words__title_threshold": "Threshold",
"words__try_again": "Try again.",

@ -954,5 +954,6 @@
"952": "setting__adjust",
"953": "setting__apply",
"954": "brightness__changed_title",
"955": "brightness__change_title"
"955": "brightness__change_title",
"956": "words__title_done"
}

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "c94efabb9554eea2adb7f312469fa8b366503bbdb57dd7f92fa67b23dc63bb97",
"datetime": "2024-07-15T09:41:37.793874",
"commit": "5935708ac86d336364e5ab78fc5fd67b19402eaf"
"merkle_root": "8c40d9e33f42783293a32e768f155e29aa3650b9bbc5e1ebaabf1ec46a7733b7",
"datetime": "2024-07-17T17:20:50.219181",
"commit": "ace22197e82214ae18255d48fae68ece0e7a0386"
},
"history": [
{

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save