From 389940e2fb203235bd0c5234133822d8d7d27784 Mon Sep 17 00:00:00 2001 From: obrusvit Date: Wed, 15 Jan 2025 15:44:08 +0100 Subject: [PATCH] feat(eckhart): header component --- .../src/ui/layout_delizia/component/frame.rs | 4 +- .../src/ui/layout_delizia/component/header.rs | 11 +- .../component/hold_to_confirm.rs | 4 +- .../rust/src/ui/layout_delizia/theme/mod.rs | 2 +- .../src/ui/layout_eckhart/component/header.rs | 272 ++++++++++++++++++ .../src/ui/layout_eckhart/component/mod.rs | 2 + .../rust/src/ui/layout_eckhart/theme/mod.rs | 33 ++- 7 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 core/embed/rust/src/ui/layout_eckhart/component/header.rs diff --git a/core/embed/rust/src/ui/layout_delizia/component/frame.rs b/core/embed/rust/src/ui/layout_delizia/component/frame.rs index 4bc2539bdf..6a5ad10144 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/frame.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/frame.rs @@ -20,7 +20,7 @@ use crate::{ }, }; -use super::super::theme::TITLE_HEIGHT; +use super::super::theme::HEADER_HEIGHT; #[derive(Clone)] pub struct HorizontalSwipe { @@ -399,7 +399,7 @@ fn frame_place( bounds: Rect, margin: usize, ) -> Rect { - let (mut header_area, mut content_area) = bounds.split_top(TITLE_HEIGHT); + let (mut header_area, mut content_area) = bounds.split_top(HEADER_HEIGHT); content_area = content_area .inset(Insets::top(theme::SPACING)) .inset(Insets::top(margin as i16)); diff --git a/core/embed/rust/src/ui/layout_delizia/component/header.rs b/core/embed/rust/src/ui/layout_delizia/component/header.rs index 83a4dcf929..12f74ff994 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/header.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/header.rs @@ -13,7 +13,7 @@ use crate::{ use super::super::{ component::{Button, ButtonMsg, ButtonStyleSheet}, - theme::{self, TITLE_HEIGHT}, + theme::{self, HEADER_HEIGHT}, }; const ANIMATION_TIME_MS: u32 = 1000; @@ -103,6 +103,7 @@ impl Header { button_msg: HeaderMsg::Cancelled, } } + #[inline(never)] pub fn with_subtitle(mut self, subtitle: TString<'static>) -> Self { let style = theme::TEXT_SUB_GREY; @@ -110,12 +111,14 @@ impl Header { 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() { @@ -123,11 +126,13 @@ impl Header { } self } + #[inline(never)] pub fn update_title(&mut self, ctx: &mut EventCtx, title: TString<'static>) { self.title.set_text(title); ctx.request_paint(); } + #[inline(never)] pub fn update_subtitle( &mut self, @@ -160,8 +165,8 @@ impl Header { self.button_msg = msg; self } - #[inline(never)] + #[inline(never)] pub fn button_styled(mut self, style: ButtonStyleSheet) -> Self { if self.button.is_some() { self.button = Some(self.button.unwrap().styled(style)); @@ -185,7 +190,7 @@ impl Component for Header { fn place(&mut self, bounds: Rect) -> Rect { let header_area = if let Some(b) = &mut self.button { - let (rest, button_area) = bounds.split_right(TITLE_HEIGHT); + let (rest, button_area) = bounds.split_right(HEADER_HEIGHT); b.place(button_area); rest } else { diff --git a/core/embed/rust/src/ui/layout_delizia/component/hold_to_confirm.rs b/core/embed/rust/src/ui/layout_delizia/component/hold_to_confirm.rs index 248d1a94d5..f2f25aa492 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/hold_to_confirm.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/hold_to_confirm.rs @@ -13,7 +13,7 @@ use crate::{ }; use super::{ - theme::{self, TITLE_HEIGHT}, + theme::{self, HEADER_HEIGHT}, Button, ButtonContent, ButtonMsg, }; @@ -210,7 +210,7 @@ impl Component for HoldToConfirm { Offset::uniform(80), Alignment2D::CENTER, )); - self.title.place(screen().split_top(TITLE_HEIGHT).0); + self.title.place(screen().split_top(HEADER_HEIGHT).0); bounds } diff --git a/core/embed/rust/src/ui/layout_delizia/theme/mod.rs b/core/embed/rust/src/ui/layout_delizia/theme/mod.rs index d7f1be9e45..56fc888a08 100644 --- a/core/embed/rust/src/ui/layout_delizia/theme/mod.rs +++ b/core/embed/rust/src/ui/layout_delizia/theme/mod.rs @@ -795,7 +795,7 @@ pub const TEXT_CHECKLIST_DONE: TextStyle = TextStyle::new(fonts::FONT_SUB, GREY, /// the header. [px] pub const SPACING: i16 = 2; -pub const TITLE_HEIGHT: i16 = 42; +pub const HEADER_HEIGHT: i16 = 42; pub const CONTENT_BORDER: i16 = 0; pub const BUTTON_HEIGHT: i16 = 62; pub const BUTTON_WIDTH: i16 = 78; diff --git a/core/embed/rust/src/ui/layout_eckhart/component/header.rs b/core/embed/rust/src/ui/layout_eckhart/component/header.rs new file mode 100644 index 0000000000..a191f17897 --- /dev/null +++ b/core/embed/rust/src/ui/layout_eckhart/component/header.rs @@ -0,0 +1,272 @@ +use crate::{ + strutil::TString, + time::{Duration, Stopwatch}, + ui::{ + component::{text::TextStyle, Component, Event, EventCtx, Label}, + display::{Color, Icon}, + geometry::{Alignment2D, Insets, Offset, Rect}, + layout_eckhart::constant, + lerp::Lerp, + shape::{self, Renderer}, + util::animation_disabled, + }, +}; + +use super::{ + button::{Button, ButtonContent, ButtonMsg}, + theme, +}; + +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; + +/// Component for the header of a screen. Eckhart UI shows the title (can be two +/// lines), optional icon button on the left, and optional icon button +/// (typically for menu) on the right. +pub struct Header { + area: Rect, + title: Label<'static>, + title_style: TextStyle, + /// button in the top-right corner + right_button: Option