From 2e6ae22ee170ae2cd6eab38af011c27c0496f4aa Mon Sep 17 00:00:00 2001 From: obrusvit Date: Sun, 23 Feb 2025 17:19:20 +0100 Subject: [PATCH] feat(eckhart): add easing and rollback to HtC anim - also switch corner icons to 4px width --- .../src/ui/layout_eckhart/component/header.rs | 2 +- .../component/hold_to_confirm.rs | 288 ++++++++++++------ .../ui/layout_eckhart/cshape/screen_border.rs | 19 +- .../src/ui/layout_eckhart/res/border/BL.png | Bin 522 -> 578 bytes .../src/ui/layout_eckhart/res/border/BL.toif | Bin 269 -> 303 bytes .../src/ui/layout_eckhart/res/border/BR.png | Bin 536 -> 573 bytes .../src/ui/layout_eckhart/res/border/BR.toif | Bin 293 -> 330 bytes .../src/ui/layout_eckhart/res/border/TL.png | Bin 259 -> 263 bytes .../src/ui/layout_eckhart/res/border/TL.toif | Bin 105 -> 109 bytes .../src/ui/layout_eckhart/res/border/TR.png | Bin 256 -> 245 bytes .../src/ui/layout_eckhart/res/border/TR.toif | Bin 101 -> 112 bytes .../rust/src/ui/layout_eckhart/theme/mod.rs | 2 +- 12 files changed, 214 insertions(+), 97 deletions(-) diff --git a/core/embed/rust/src/ui/layout_eckhart/component/header.rs b/core/embed/rust/src/ui/layout_eckhart/component/header.rs index c74ced651d..6d28561e1e 100644 --- a/core/embed/rust/src/ui/layout_eckhart/component/header.rs +++ b/core/embed/rust/src/ui/layout_eckhart/component/header.rs @@ -92,7 +92,7 @@ pub enum HeaderMsg { impl Header { pub const HEADER_HEIGHT: i16 = 96; // [px] pub const HEADER_BUTTON_WIDTH: i16 = 56; // [px] - const HEADER_INSETS: Insets = Insets::sides(24); // [px] + pub const HEADER_INSETS: Insets = Insets::sides(24); // [px] pub const fn new(title: TString<'static>) -> Self { Self { diff --git a/core/embed/rust/src/ui/layout_eckhart/component/hold_to_confirm.rs b/core/embed/rust/src/ui/layout_eckhart/component/hold_to_confirm.rs index 92f43f844d..1f90e0dbdb 100644 --- a/core/embed/rust/src/ui/layout_eckhart/component/hold_to_confirm.rs +++ b/core/embed/rust/src/ui/layout_eckhart/component/hold_to_confirm.rs @@ -5,20 +5,21 @@ use crate::{ component::{Component, Event, EventCtx, Never}, display::Color, geometry::{Offset, Rect}, + lerp::Lerp, shape::{self, Renderer}, }, }; use super::{ - super::{component::Header, cshape::ScreenBorder, fonts, theme}, + super::{component::Header, cshape::ScreenBorder, theme}, constant::SCREEN, }; /// A component that displays a border that grows from the bottom of the screen /// to the top. The animation is parametrizable by color and duration. pub struct HoldToConfirmAnim { - /// Duration of the animation - duration: Duration, + /// Intended total duration of Hold to Confirm animation + total_duration: Duration, /// Screen border and header overlay color color: Color, /// Screen border shape @@ -27,17 +28,50 @@ pub struct HoldToConfirmAnim { timer: Stopwatch, /// Header overlay text shown during the animation header_overlay: Option>, + /// Rollback animation state + rollback: RollbackState, +} + +/// State of the rollback animation, when `stop` is called. +struct RollbackState { + /// Timer for the rollback animation + timer: Stopwatch, + /// Point in time of the growth animation when the rollback was initiated + duration: Duration, } impl HoldToConfirmAnim { + /// Growth duration of the bottom part of the border + const BOTTOM_DURATION: Duration = Duration::from_millis(200); + /// Growth duration of the side parts of the border + const SIDES_DURATION: Duration = Duration::from_millis(800); + /// Growth duration of the top part of the border + const TOP_DURATION: Duration = Duration::from_millis(600); + + /// Duration of the rollback animation after `stop` is called + const ROLLBACK_DURATION: Duration = Duration::from_millis(600); + + /// Duration after which the header overlay is shown after `start` is called + const HEADER_OVERLAY_DELAY: Duration = Duration::from_millis(300); + pub fn new() -> Self { + debug_assert!( + theme::CONFIRM_HOLD_DURATION.to_millis() + > (Self::BOTTOM_DURATION.to_millis() + + Self::SIDES_DURATION.to_millis() + + Self::TOP_DURATION.to_millis()) + ); let default_color = theme::GREEN_LIME; Self { - duration: theme::CONFIRM_HOLD_DURATION, + total_duration: theme::CONFIRM_HOLD_DURATION, color: default_color, border: ScreenBorder::new(default_color), timer: Stopwatch::default(), header_overlay: None, + rollback: RollbackState { + timer: Stopwatch::default(), + duration: Duration::default(), + }, } } @@ -48,7 +82,13 @@ impl HoldToConfirmAnim { } pub fn with_duration(mut self, duration: Duration) -> Self { - self.duration = duration; + debug_assert!( + duration.to_millis() + > (Self::BOTTOM_DURATION.to_millis() + + Self::SIDES_DURATION.to_millis() + + Self::TOP_DURATION.to_millis()) + ); + self.total_duration = duration; self } @@ -62,64 +102,19 @@ impl HoldToConfirmAnim { } pub fn stop(&mut self) { + self.rollback.timer = Stopwatch::new_started(); + self.rollback.duration = self.timer.elapsed(); self.timer = Stopwatch::new_stopped(); } fn is_active(&self) -> bool { - self.timer.is_running_within(self.duration) + self.timer.is_running_within(self.total_duration) } - fn get_clips(&self) -> (Rect, Option) { - let ratio = self.timer.elapsed() / self.duration; - - let bottom_width = self.border.bottom_width(); - let total_height = SCREEN.height(); - let total_width = SCREEN.width(); - - let circumference = 2 * total_height + total_width + bottom_width; - let bottom_ratio = bottom_width as f32 / circumference as f32; - let vertical_ratio = (2 * total_height) as f32 / circumference as f32; - let upper_ratio = total_width as f32 / circumference as f32; - - let vertical_cut = bottom_ratio + vertical_ratio; - - if ratio < bottom_ratio { - // Animate the bottom border growing horizontally. - let clip_width = ((ratio / bottom_ratio) * bottom_width as f32) as i16; - let clip_width = clip_width.clamp(0, bottom_width); - ( - Rect::from_center_and_size( - SCREEN - .bottom_center() - .ofs(Offset::y(-ScreenBorder::WIDTH / 2)), - Offset::new(clip_width, ScreenBorder::WIDTH), - ), - None, - ) - } else if ratio < vertical_cut { - // Animate the vertical border growing from the bottom up. - let progress = (ratio - bottom_ratio) / vertical_ratio; - let clip_height = (progress * total_height as f32) as i16; - let clip_height = clip_height.clamp(0, total_height - ScreenBorder::WIDTH); - ( - Rect::from_bottom_left_and_size( - SCREEN.bottom_left(), - Offset::new(total_width, clip_height), - ), - None, - ) - } else { - // Animate the top border growing horizontally towards center. - let progress = (ratio - vertical_cut) / upper_ratio; - let clip_width = total_width - ((progress * total_width as f32) as i16); - ( - SCREEN, - Some(Rect::from_center_and_size( - SCREEN.top_center().ofs(Offset::y(ScreenBorder::WIDTH / 2)), - Offset::new(clip_width, ScreenBorder::WIDTH), - )), - ) - } + fn is_rollback(&self) -> bool { + self.rollback + .timer + .is_running_within(Self::ROLLBACK_DURATION) } } @@ -132,7 +127,7 @@ impl Component for HoldToConfirmAnim { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event { - if self.is_active() { + if self.is_active() || self.is_rollback() { ctx.request_anim_frame(); ctx.request_paint(); } @@ -141,37 +136,160 @@ impl Component for HoldToConfirmAnim { } fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // Rollback & Fading out animation + if self.is_rollback() { + let rollback_elapsed = self.rollback.timer.elapsed(); + let alpha = self.get_rollback_alpha(rollback_elapsed); + let rollback_duration_progressed = self + .rollback + .duration + .checked_add(rollback_elapsed) + .unwrap_or(Duration::default()); + let (clip, top_gap) = self.get_clips(rollback_duration_progressed); + let top_back_rollback = self.get_top_gap_rollback(rollback_elapsed); + let top_gap = top_gap.union(top_back_rollback); + self.render_clipped_border(clip, top_gap, alpha, target); + } + + // Growing animation if self.is_active() { // override header with custom text - if let Some(text) = self.header_overlay { - let font = fonts::FONT_SATOSHI_REGULAR_22; - let header_pad = Rect::from_top_left_and_size( - SCREEN.top_left(), - Offset::new(SCREEN.width(), Header::HEADER_HEIGHT), - ); - shape::Bar::new(header_pad) - .with_bg(theme::BG) - .render(target); - text.map(|text| { - let text_pos = header_pad.top_left() - + Offset::new(24, font.vert_center(0, Header::HEADER_HEIGHT, text)); - shape::Text::new(text_pos, text, font) - .with_fg(self.color) - .render(target); - }); + if self.timer.elapsed() > Self::HEADER_OVERLAY_DELAY { + self.render_header_overlay(target); } // growing border - let (in_clip, out_clip_opt) = self.get_clips(); - target.in_clip(in_clip, &|target| { - self.border.render(target); - }); - // optional out clip for upper line rendering - if let Some(out_clip) = out_clip_opt { - shape::Bar::new(out_clip) - .with_bg(theme::BG) - .with_fg(theme::BG) - .render(target); - } + let (clip, top_gap) = self.get_clips(self.timer.elapsed()); + self.render_clipped_border(clip, top_gap, u8::MAX, target); + } + } +} + +// Rendering helpers +impl HoldToConfirmAnim { + fn render_header_overlay<'s>(&'s self, target: &mut impl Renderer<'s>) { + if let Some(text) = self.header_overlay { + let font = theme::label_title_main().text_font; + let header_pad = Rect::from_top_left_and_size( + SCREEN.top_left(), + Offset::new(SCREEN.width(), Header::HEADER_HEIGHT), + ); + // FIXME: vert_center is precisely aligned with the `Header` title (which uses `Label`) + // but this solution might break with `Header` changes + let text_offset = Offset::new( + Header::HEADER_INSETS.left, + font.vert_center(0, Header::HEADER_HEIGHT - 1, "A"), + ); + let text_pos = header_pad.top_left() + text_offset; + shape::Bar::new(header_pad) + .with_bg(theme::BG) + .render(target); + text.map(|text| { + shape::Text::new(text_pos, text, font) + .with_fg(self.color) + .render(target); + }); + } + } + + fn render_clipped_border<'s>( + &'s self, + clip: Rect, + top_gap: Rect, + alpha: u8, + target: &mut impl Renderer<'s>, + ) { + target.in_clip(clip, &|target| { + self.border.render(alpha, target); + }); + // optional out clip for upper line rendering + shape::Bar::new(top_gap) + .with_bg(theme::BG) + .with_fg(theme::BG) + .render(target); + } + + fn get_rollback_alpha(&self, elapsed: Duration) -> u8 { + let progress = (elapsed / Self::ROLLBACK_DURATION).clamp(0.0, 1.0); + let shift = pareen::constant(0.0).seq_ease_out( + 0.0, + easer::functions::Cubic, + 1.0, + pareen::constant(1.0), + ); + u8::lerp(u8::MAX, u8::MIN, shift.eval(progress)) + } + + fn get_top_gap_rollback(&self, elapsed: Duration) -> Rect { + let progress = (elapsed / Self::ROLLBACK_DURATION).clamp(0.0, 1.0); + let clip_width = (progress * SCREEN.width() as f32) as i16; + Rect::from_center_and_size( + SCREEN.top_center().ofs(Offset::y(ScreenBorder::WIDTH / 2)), + Offset::new(clip_width, ScreenBorder::WIDTH), + ) + } + + fn get_clips(&self, elapsed: Duration) -> (Rect, Rect) { + // Define segment-specific timings + let bottom_dur_ratio = Self::BOTTOM_DURATION / self.total_duration; + let sides_dur_ratio = Self::SIDES_DURATION / self.total_duration; + let top_dur_ratio = Self::TOP_DURATION / self.total_duration; + + let progress = (elapsed / self.total_duration).clamp(0.0, 1.0); + + const TOP_GAP_ZERO: Rect = Rect::from_center_and_size( + SCREEN.top_center().ofs(Offset::y(ScreenBorder::WIDTH / 2)), + Offset::zero(), + ); + const TOP_GAP_FULL: Rect = Rect::from_center_and_size( + SCREEN.top_center().ofs(Offset::y(ScreenBorder::WIDTH / 2)), + Offset::new(SCREEN.width(), ScreenBorder::WIDTH), + ); + match progress { + // Bottom phase growing linearly + p if p < bottom_dur_ratio => { + let bottom_progress = (p / bottom_dur_ratio).clamp(0.0, 1.0); + let width = i16::lerp(0, SCREEN.width(), bottom_progress); + let clip = Rect::from_center_and_size( + SCREEN + .bottom_center() + .ofs(Offset::y(-ScreenBorder::WIDTH / 2)), + Offset::new(width, ScreenBorder::WIDTH), + ); + (clip, TOP_GAP_FULL) + } + + // Sides phase growing up linearly + p if p < (bottom_dur_ratio + sides_dur_ratio) => { + let sides_progress = ((p - bottom_dur_ratio) / sides_dur_ratio).clamp(0.0, 1.0); + let height = i16::lerp(ScreenBorder::WIDTH, SCREEN.height(), sides_progress); + let clip = Rect::from_bottom_left_and_size( + SCREEN.bottom_left(), + Offset::new(SCREEN.width(), height), + ); + (clip, TOP_GAP_FULL) + } + + // Top phase + p if p < 1.0 => { + let top_progress = + ((p - bottom_dur_ratio - sides_dur_ratio) / top_dur_ratio).clamp(0.0, 1.0); + let ease = pareen::constant(0.0).seq_ease_out( + 0.0, + easer::functions::Cubic, + 1.0, + pareen::constant(1.0), + ); + let eased_progress = ease.eval(top_progress); + let width = i16::lerp(SCREEN.width(), 0, eased_progress); + let top_gap = Rect::from_center_and_size( + SCREEN.top_center().ofs(Offset::y(ScreenBorder::WIDTH / 2)), + Offset::new(width, ScreenBorder::WIDTH), + ); + (SCREEN, top_gap) + } + + // Animation complete + _ => (SCREEN, TOP_GAP_ZERO), } } } diff --git a/core/embed/rust/src/ui/layout_eckhart/cshape/screen_border.rs b/core/embed/rust/src/ui/layout_eckhart/cshape/screen_border.rs index 6ebf4c918b..b36a9836ab 100644 --- a/core/embed/rust/src/ui/layout_eckhart/cshape/screen_border.rs +++ b/core/embed/rust/src/ui/layout_eckhart/cshape/screen_border.rs @@ -16,7 +16,7 @@ pub struct ScreenBorder { } impl ScreenBorder { - pub const WIDTH: i16 = 2; + pub const WIDTH: i16 = 4; pub fn new(color: Color) -> Self { let screen = constant::screen(); @@ -26,14 +26,14 @@ impl ScreenBorder { x0: screen.x0 + ICON_BORDER_TL.toif.width(), y0: screen.y0, x1: screen.x1 - ICON_BORDER_TR.toif.width(), - y1: screen.y0 + 2, + y1: screen.y0 + Self::WIDTH, }; // Bottom bar: from the right edge of bottom-left icon to the left edge of // bottom-right icon. let bottom_bar_rect = Rect { x0: screen.x0 + ICON_BORDER_BL.toif.width(), - y0: screen.y1 - 2, + y0: screen.y1 - Self::WIDTH, x1: screen.x1 - ICON_BORDER_BR.toif.width(), y1: screen.y1, }; @@ -42,15 +42,15 @@ impl ScreenBorder { // bottom-left icon. let left_bar_rect = Rect { x0: screen.x0, - y0: screen.y0 + ICON_BORDER_TL.toif.height() - 1, - x1: screen.x0 + 2, + y0: screen.y0 + ICON_BORDER_TL.toif.height(), + x1: screen.x0 + Self::WIDTH, y1: screen.y1 - ICON_BORDER_BL.toif.height(), }; // Right bar: from the bottom edge of top-right icon to the top edge of // bottom-right icon. let right_bar_rect = Rect { - x0: screen.x1 - 2, - y0: screen.y0 + ICON_BORDER_TR.toif.height() - 1, + x0: screen.x1 - Self::WIDTH, + y0: screen.y0 + ICON_BORDER_TR.toif.height(), x1: screen.x1, y1: screen.y1 - ICON_BORDER_BR.toif.height(), }; @@ -70,8 +70,7 @@ impl ScreenBorder { // Draw the four side bars. self.side_bars.iter().for_each(|bar| { shape::Bar::new(*bar) - .with_fg(self.color) - .with_thickness(2) + .with_bg(self.color) .with_alpha(alpha) .render(target); }); @@ -102,8 +101,8 @@ impl ScreenBorder { .iter() .for_each(|(position, toif, alignment)| { shape::ToifImage::new(*position, *toif) - .with_fg(self.color) .with_align(*alignment) + .with_fg(self.color) .with_alpha(alpha) .render(target); }); diff --git a/core/embed/rust/src/ui/layout_eckhart/res/border/BL.png b/core/embed/rust/src/ui/layout_eckhart/res/border/BL.png index fd2a7f2da9e5ecd159742b256e88ed798d01a6e9..03f3117b263f11bcb5d96983c4018bb54e30e695 100644 GIT binary patch delta 509 zcmV5;NY4ngRd-0nDgkWO`ypbH=r zyX7Gd8kam|t1;2IM=truYYq}QDieSRvy6tW{-IBnhdk(0$wRh$q*qBX4j{>SM?)s9 zgP_yD$G!EEhkV!B@l}%cIcA@rc+B$F9S`}U^DWP~qqjIQtkn$0i=MTb=_&Y4=M1aK zt4-EwCdQN1tba&I#M->$Sr0p%cd>%B3Uw^wz_ZTxJeB-HN}Akg>&JIUpY>|a){{Ln z()y9{qW>S(8CgF@ZWQ^H!FrCpzWV!b&wVc0=}c^MCR_;|NoCRFRl|y>GfRrPaVh%k zYu2OA^GMNoXsyVahjc-wmJNApCbb%1tb&dR5dCT!0^>sH)k zy<%i1C&pClgEV9$AXawYv82DSrGi6=B@FNdlugK~z}7?U-Rv!$1^-ztjLHAt)dYI0=zZ z2Sh?0fCGvHs-U1CK|z9o1_jHHq@|q#Kj_{}GQ$(_&F*`9?~+53u-3wh8<0IHc#$7~ z8JWO%3*?hG-p>NrVee%RT=|jLP9(}DwSJYssuHx5oTq#N?tic^fCebM$PQ~V=Yb?e zG6BgMqhYJRJFO4M24jo{WamdVPUAqTnHyKf{vL0zb#sq=^CKG|dXQtK8&K{vZnXY| z++Z5esmfai@=yw{xF1wzy{ntYU%{^) z(PNfM;#Eq}T5f5;86^jqJ0k>OJ7XU4OB9@fXHN1Jf~o=6h-DV_D7o=euiomXX$ zwDZ0np{=CKi}YV^m!|W3T-8>zumI z(hIFpocCJN%8N|4KkvQBLXuQ?p${_((|m>${pMTGQ$E$Hc`sB!wWL-{FLW(QdbCDg vOSO`uT9u+HSjx2?v14T>wIz{)8v=X+BF%wz!_Lxn00000NkvXXu0mjfn_bh% diff --git a/core/embed/rust/src/ui/layout_eckhart/res/border/BL.toif b/core/embed/rust/src/ui/layout_eckhart/res/border/BL.toif index 6fdc72a94f9892b721864efac67c0fdb692d1369..53230c1b946870d612674e4137700fe3bf13607f 100644 GIT binary patch literal 303 zcmV+~0nq+bPf1590AK(k0RRBSlFdy5Q5Z#M5CKJDDnJ`R?LfA!m=2@^=m0y=4lq_= z2gIc-OBktO;=eN^TwQYy$xDp!El={^cklV06!{Usm12UmKv=0xaNA+2hQmTFhacK; z_)hRmdk&wq@9;?-hmSfScg_?(ks?>M*qKBC7_vwD#GyrG8F#jgFQ8qnjnbD7{~GzX4Kh Blr#VU literal 269 zcmV+o0rLJ-Pf1590AK(C0RRBSlF1E%P!vT45j74_f!Ki265P3j4zL6|&SLPt8s&E=Z_x5kUhICprSKCBzb+jFE0HH# zp%QxP(aL53y-1wi6MK@M|L;eAfC1Y7$TOuyj#QP#CqF6?7kzQjlurK-0PZ6bLd{lS TV45E%<-0byQdW6Yhr;>->+W-B diff --git a/core/embed/rust/src/ui/layout_eckhart/res/border/BR.png b/core/embed/rust/src/ui/layout_eckhart/res/border/BR.png index 59d94fbb4b204ccf3e0dbf50901b66f90f9a6652..76da10a6cdb41c5496b5fe47682a3fb6ad74fe7f 100644 GIT binary patch delta 504 zcmV5;Q>W51{}60nSN8K~z}7<(biK!axj$P1W~igzE&&2F(Z= zAtQ8yutB>)vjJ?-bOOQzlnD|ha38RbP-q{xFA}JJF$&VB|3BMzC*4f2P~GP{4(Wj* zHM1WBr}DOtJ01yE+>sj&87yzF+PJIy;*bwK(v1%oQG4A-mVb;gU}nIcD^IZe#Upn- zvb$>IPdxJRfJ7p;kcv<$bITov%q(v?WWlTSdUps-WVB`E6fhjN49d7`Tl2_{Lq1z> z0oFeF;^fE0A&k4WHHR!Lw~%cS-I(zr8D+pQ-qsl7uI-aYwvg>9R`R4L(~Y~fbqeHI z<%g-PC}2DnHGfdZ(tw-e?B5YrnrR-*;$@F1jD`_#PUx%Sy-anHs;$(w5l(fL@FJq;qDgvUIl=YG uckmy$dHNI3y@-emvXxer(#?icpvX6)c%6XRZ$Kyj0000wn3Cl^2--8!xf|nyVLo z^dqw^(zqlEj44$BjTO0l*u2OAQVxLJjgW2apA2Wp0{HMF8!s{db1%|fPX1MnD`ml< z92YWhI7yfIQc`dNv;DK?6A!?`kDME|hd9ZTne>PUj%Lz_oEWu1QBv*%21)UBFIBwc zHU=nu-%S%ANPoL@Yqvl5Q^ZSPdIQ?=w_o~;*Vx;R?jaB7wEK%!z>70uE}ehU`lSrO zVzhFmjFKuR@Ip0x#Vg>=nek#Z{g7ixuSVbyx2BS`^k=Nx8Dl!<7&-gn5$~I^wrBK? z_M0d>yjs=i!uJ4lteG>V8FlH$_X+*;Y@|udq~QPn002ov JPDHLkV1o2i+_eAz diff --git a/core/embed/rust/src/ui/layout_eckhart/res/border/BR.toif b/core/embed/rust/src/ui/layout_eckhart/res/border/BR.toif index a601627196f350b48f626d390f5eeb7461c4ed16..37314093ed3a8b5e550d936850d38c721d8a01fb 100644 GIT binary patch literal 330 zcmV-Q0k!^APf1590AK(<0RRAvlFd=VU=&3YLt_wLV&qSg1NW)ueBwc~K4hQqP89To&X2$rfl zoT}#Vli-&&2v!}=wdQcC*jS=h|8QSJhbIJk8aTXC-{G~|4xe;DZ8U>3-U?1`o$~OI zQpy9ZpOf`)mt(rQl3dQn&;nXdCI7HP+B01`%yk?nb3!udI1{M?X+DyoL7<{S4a%ra z8F}f4)bBsb+jMb(%YVA4LKootR(*=SMSDExKliNKl;wRUn}*CFFwcB>Ov^dqT;x cw7J4gU!-CDm+ANIMh?#+hZnKIha+RrH{zw882|tP literal 293 zcmV+=0owjlPf1590AK(a0RRA{lDlofP!xvEQy9#nPUQunumLC?6>I<-gb84Sm<{j< z6p>Pf4L}S4RcgZ9!Q9;R+^_g0Tz>LB=YJf}bH1OtC&FC~;Jym*5CA;N@azLjN&tTe zUW)+JJiuEH;9tgNjV=7xTmWnn98LjF#sFu1fb*7G5e9#%sBxwRfF>vAp<73nOLDoV zn>z*SDcqp-hJ_z{q^<(oyT_FaR>ebnG}Mq diff --git a/core/embed/rust/src/ui/layout_eckhart/res/border/TL.png b/core/embed/rust/src/ui/layout_eckhart/res/border/TL.png index bba7695132118332e12761962f25217c7df812d5..0dae0e8dd9e334d6b2443dd75bab958f38260c2a 100644 GIT binary patch delta 206 zcmV;<05SiA0*3;S7YY&x1ONa469p~eks&UB7!MLN-in$60001(Nkl5|@Q!`J}0001#NklC5E(BHwlEoqNoH*MzD^Ran^C*u549<8#X%;HuI?4)!SMq6%mr2^tyX&uZ04 Prn02+PmSs!R)IoZYG5sg literal 105 zcmV-v0G9t$Pf14;022UR0001EU;u%{lYjZaj4l8F{|B=+|3Bx>1Y#8Y?_&f@3H+K5 zVK5wg&jMjE{L+Oo3hpyOS)cr&42ItvP=*6sazj2$@{>M{^^FI{`o#{{3bTOWA51I5 Le;9+o13|PAN?D=kqpybLJ)iBt6wMYt%i=+8^J$ zQ9KNwk~|FHyjM&VF0`NLWAC4t>jq9;C^7K(6xAPT15Xx=Oe@U3VwE+@+AB7!|MsFM quQwb_H!k;Rv$ovqV=5|@Q!`J}0001yNklmG=fc-3BUx>02)Ap;}_=Urd&OrfBWC<>(doz2PJvpg*hKOFb^C_amNqxjHJEa|SfnyyG-he~QMxcq=45TV#DEz_-JOR_vbO>SorkDT#002ovPDHLkV1h5f BQ#Swr diff --git a/core/embed/rust/src/ui/layout_eckhart/res/border/TR.toif b/core/embed/rust/src/ui/layout_eckhart/res/border/TR.toif index 9fe8c1ec898b70e99497c5e3688b8168c87b39e3..3d5856c2a7564850ba52bca76021b61136d1f7a1 100644 GIT binary patch literal 112 zcmV-$0FVDvPf14;01^OX0002{zb})C0Sx~C|G%CE#`?dX3C8+g4;FN~0c1)+lrH}N z|2!jvdF}syE(lBEFH|K1!@>Xmnc;%mP{A+%{h_Q4|Mx>#4*&l{&0+ZcpAE|T@t+gQ S`ud*-%KG%57s~qZpC16d%R7?* literal 101 zcmV-r0Gj_)Pf14;022UN0002{zb})C0Sx~C|G%CE!gMRrDu>}6X%whP=24j8agt5Nxz*wJnVXP1Q Ha9daa3tKTX diff --git a/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs b/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs index 47348af6f0..17be168d4e 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs @@ -12,7 +12,7 @@ use super::{ fonts, }; -pub const CONFIRM_HOLD_DURATION: Duration = Duration::from_millis(1500); +pub const CONFIRM_HOLD_DURATION: Duration = Duration::from_millis(2000); pub const ERASE_HOLD_DURATION: Duration = Duration::from_millis(1500); // Color palette.