From c605ea9838da59fb5634973636a45b591f1e138e Mon Sep 17 00:00:00 2001 From: obrusvit Date: Mon, 25 Mar 2024 10:26:27 +0100 Subject: [PATCH] feat(core): T3T1 Instructions component --- core/embed/rust/src/ui/display/font.rs | 10 ++ .../model_mercury/component/instructions.rs | 129 ++++++++++++++++++ .../src/ui/model_mercury/component/mod.rs | 2 + core/embed/rust/src/ui/shape/text.rs | 12 -- 4 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 core/embed/rust/src/ui/model_mercury/component/instructions.rs diff --git a/core/embed/rust/src/ui/display/font.rs b/core/embed/rust/src/ui/display/font.rs index 0cca5a95a..5a88a4b30 100644 --- a/core/embed/rust/src/ui/display/font.rs +++ b/core/embed/rust/src/ui/display/font.rs @@ -293,6 +293,16 @@ impl Font { text.len() // it fits in its entirety } + + pub fn visible_text_height_ex(&self, text: &str) -> (i16, i16) { + let (mut ascent, mut descent) = (0, 0); + for c in text.chars() { + let glyph = self.get_glyph(c); + ascent = ascent.max(glyph.bearing_y); + descent = descent.max(glyph.height - glyph.bearing_y); + } + (ascent, descent) + } } pub trait GlyphMetrics { diff --git a/core/embed/rust/src/ui/model_mercury/component/instructions.rs b/core/embed/rust/src/ui/model_mercury/component/instructions.rs new file mode 100644 index 000000000..7829f7000 --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/component/instructions.rs @@ -0,0 +1,129 @@ +use crate::{ + strutil::TString, + ui::{ + component::{text::TextStyle, Component, Event, EventCtx, Never}, + constant::WIDTH, + geometry::{Alignment, Offset, Rect}, + model_mercury::theme, + shape::{Renderer, Text}, + }, +}; + +/// Component showing a task instruction (e.g. "Swipe up") and optionally task +/// description (e.g. "Confirm transaction") to a user. The component +/// is typically placed at the bottom of the screen. The height of the provided +/// area must be 18px (only instruction) or 37px (both description and +/// instruction). The content and style of both description and instruction is +/// configurable separatedly. +pub struct Instructions<'a> { + area: Rect, + text_instruction: TString<'a>, + text_description: Option>, + style_instruction: &'static TextStyle, + style_description: &'static TextStyle, +} + +impl<'a> Instructions<'a> { + /// height for component with only instruction [px] + pub const HEIGHT_SIMPLE: i16 = 18; + /// height for component with both description and instruction [px] + pub const HEIGHT_DEFAULT: i16 = 37; + + pub fn new>>(instruction: T) -> Self { + Self { + area: Rect::zero(), + text_instruction: instruction.into(), + text_description: None, + style_instruction: &theme::TEXT_SUB, + style_description: &theme::TEXT_SUB, + } + } + + pub fn with_description>>(self, description: T) -> Self { + Self { + text_description: Some(description.into()), + ..self + } + } + + pub fn update_instruction>>(&mut self, ctx: &mut EventCtx, s: T) { + self.text_instruction = s.into(); + ctx.request_paint(); + } + + pub fn update_description>>(&mut self, ctx: &mut EventCtx, s: T) { + self.text_description = Some(s.into()); + ctx.request_paint(); + } + + pub fn update_instruction_style(&mut self, ctx: &mut EventCtx, style: &'static TextStyle) { + self.style_instruction = style; + ctx.request_paint(); + } + + pub fn update_description_style(&mut self, ctx: &mut EventCtx, style: &'static TextStyle) { + self.style_description = style; + ctx.request_paint(); + } +} + +impl<'a> Component for Instructions<'a> { + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + let h = bounds.height(); + assert!(h == Instructions::HEIGHT_SIMPLE || h == Instructions::HEIGHT_DEFAULT); + assert!(bounds.width() == WIDTH); + self.area = bounds; + bounds + } + + fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option { + None + } + + fn paint(&mut self) { + // TODO: remove when ui-t3t1 done + todo!() + } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // show description only if there is space for it + if self.area.height() == Instructions::HEIGHT_DEFAULT { + if let Some(description) = self.text_description { + let area_description = self.area.split_top(Instructions::HEIGHT_SIMPLE).0; + let text_description_font_descent = self + .style_description + .text_font + .visible_text_height_ex("Ay") + .1; + let text_description_baseline = + area_description.bottom_center() - Offset::y(text_description_font_descent); + + description.map(|t| { + Text::new(text_description_baseline, t) + .with_font(self.style_description.text_font) + .with_fg(self.style_description.text_color) + .with_align(Alignment::Center) + .render(target); + }); + } + } + + let area_instruction = self.area.split_bottom(Instructions::HEIGHT_SIMPLE).1; + let text_instruction_font_descent = self + .style_instruction + .text_font + .visible_text_height_ex("Ay") + .1; + let text_instruction_baseline = + area_instruction.bottom_center() - Offset::y(text_instruction_font_descent); + self.text_instruction.map(|t| { + Text::new(text_instruction_baseline, t) + .with_font(self.style_instruction.text_font) + .with_fg(self.style_instruction.text_color) + .with_align(Alignment::Center) + .render(target); + }); + } +} diff --git a/core/embed/rust/src/ui/model_mercury/component/mod.rs b/core/embed/rust/src/ui/model_mercury/component/mod.rs index 98e8033bf..0d007a731 100644 --- a/core/embed/rust/src/ui/model_mercury/component/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/component/mod.rs @@ -6,6 +6,7 @@ mod button; mod coinjoin_progress; mod dialog; mod fido; +mod instructions; mod vertical_menu; #[rustfmt::skip] mod fido_icons; @@ -40,6 +41,7 @@ pub use fido::{FidoConfirm, FidoMsg}; pub use frame::{Frame, FrameMsg}; #[cfg(feature = "micropython")] pub use homescreen::{check_homescreen_format, Homescreen, HomescreenMsg, Lockscreen}; +pub use instructions::Instructions; pub use keyboard::{ bip39::Bip39Input, mnemonic::{MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg}, diff --git a/core/embed/rust/src/ui/shape/text.rs b/core/embed/rust/src/ui/shape/text.rs index cc83da8b2..d4feb9492 100644 --- a/core/embed/rust/src/ui/shape/text.rs +++ b/core/embed/rust/src/ui/shape/text.rs @@ -120,15 +120,3 @@ impl<'a, 's> ShapeClone<'s> for Text<'a> { Some(clone.uninit.init(Text { text, ..self })) } } - -impl Font { - fn visible_text_height_ex(&self, text: &str) -> (i16, i16) { - let (mut ascent, mut descent) = (0, 0); - for c in text.chars() { - let glyph = self.get_glyph(c); - ascent = ascent.max(glyph.bearing_y); - descent = descent.max(glyph.height - glyph.bearing_y); - } - (ascent, descent) - } -}