From 3f3fd4a021fd91c324bbc28a360f0aca08a2232c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ioan=20Biz=C4=83u?= Date: Fri, 11 Apr 2025 09:39:10 +0200 Subject: [PATCH] feat(eckhart): allow multiline text in buttons [no changelog] --- .../ui/layout_eckhart/bootloader/bld_menu.rs | 4 +- .../src/ui/layout_eckhart/component/button.rs | 141 +++++++++++++----- .../layout_eckhart/firmware/vertical_menu.rs | 4 +- 3 files changed, 108 insertions(+), 41 deletions(-) diff --git a/core/embed/rust/src/ui/layout_eckhart/bootloader/bld_menu.rs b/core/embed/rust/src/ui/layout_eckhart/bootloader/bld_menu.rs index b3d2e031ea..2c21133f68 100644 --- a/core/embed/rust/src/ui/layout_eckhart/bootloader/bld_menu.rs +++ b/core/embed/rust/src/ui/layout_eckhart/bootloader/bld_menu.rs @@ -74,7 +74,9 @@ impl Component for BldMenu { let padding = 28; for button in self.buttons.iter_mut() { - let button_height = button.content_height() + 2 * padding; + let button_height = button + .content_height(button_width - 2 * Button::MENU_ITEM_CONTENT_OFFSET.x) + + 2 * padding; let button_bounds = Rect::from_top_left_and_size(top_left, Offset::new(button_width, button_height)); diff --git a/core/embed/rust/src/ui/layout_eckhart/component/button.rs b/core/embed/rust/src/ui/layout_eckhart/component/button.rs index c8742dbe7b..051e7d9b7b 100644 --- a/core/embed/rust/src/ui/layout_eckhart/component/button.rs +++ b/core/embed/rust/src/ui/layout_eckhart/component/button.rs @@ -5,6 +5,7 @@ use crate::{ time::Duration, ui::{ component::{text::TextStyle, Component, Event, EventCtx, Timer}, + constant, display::{toif::Icon, Color, Font}, event::TouchEvent, geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect}, @@ -58,7 +59,7 @@ impl Button { ); const MENU_ITEM_RADIUS: u8 = 12; const MENU_ITEM_ALIGNMENT: Alignment = Alignment::Start; - const MENU_ITEM_CONTENT_OFFSET: Offset = Offset::x(12); + pub const MENU_ITEM_CONTENT_OFFSET: Offset = Offset::x(12); pub const fn new(content: ButtonContent) -> Self { Self { @@ -245,18 +246,36 @@ impl Button { self.style().font.visible_text_height("1") } - pub fn content_height(&self) -> i16 { + fn baseline_subtext_height(&self) -> i16 { + match &self.content { + ButtonContent::TextAndSubtext { subtext_style, .. } => { + subtext_style.text_font.visible_text_height("1") + } + _ => 0, + } + } + + fn text_height(&self, text: &str, width: i16) -> i16 { + let (t1, t2) = split_two_lines(text, self.stylesheet.normal.font, width); + if t1.is_empty() || t2.is_empty() { + self.style().font.line_height() + } else { + self.style().font.line_height() * 2 - constant::LINE_SPACE + } + } + + pub fn content_height(&self, width: i16) -> i16 { match &self.content { ButtonContent::Empty => 0, - ButtonContent::Text(_) => self.baseline_text_height(), + ButtonContent::Text(text) => text.map(|t| self.text_height(t, width)), ButtonContent::Icon(icon) => icon.toif.height(), ButtonContent::IconAndText(child) => { - let text_height = self.baseline_text_height(); + let text_height = self.style().font.line_height(); let icon_height = child.icon.toif.height(); text_height.max(icon_height) } - ButtonContent::TextAndSubtext { subtext_style, .. } => { - self.style().font.line_height() + subtext_style.text_font.text_height() + ButtonContent::TextAndSubtext { text, .. } => { + text.map(|t| self.text_height(t, width) + self.baseline_subtext_height()) } #[cfg(feature = "micropython")] ButtonContent::HomeBar(_) => theme::ACTION_BAR_HEIGHT, @@ -371,21 +390,45 @@ impl Button { stylesheet: &ButtonStyle, alpha: u8, ) { + let mut show_text = |text: &str, render_origin: Point| { + shape::Text::new(render_origin, text, stylesheet.font) + .with_fg(stylesheet.text_color) + .with_align(self.text_align) + .with_alpha(alpha) + .render(target) + }; + let render_origin = |y_offset: i16| { + match self.text_align { + Alignment::Start => self.area.left_center().ofs(self.content_offset), + Alignment::Center => self.area.center().ofs(self.content_offset), + Alignment::End => self.area.right_center().ofs(self.content_offset.neg()), + } + .ofs(Offset::y(y_offset)) + }; + match &self.content { ButtonContent::Empty => {} ButtonContent::Text(text) => { - let render_origin = match self.text_align { - Alignment::Start => self.area.left_center().ofs(self.content_offset), - Alignment::Center => self.area.center().ofs(self.content_offset), - Alignment::End => self.area.right_center().ofs(self.content_offset.neg()), - } - .ofs(Offset::y(self.content_height() / 2)); - text.map(|text| { - shape::Text::new(render_origin, text, stylesheet.font) - .with_fg(stylesheet.text_color) - .with_align(self.text_align) - .with_alpha(alpha) - .render(target); + let text_baseline_height = self.baseline_text_height(); + text.map(|t| { + let (t1, t2) = split_two_lines( + t, + stylesheet.font, + self.area.width() - 2 * self.content_offset.x, + ); + + if t1.is_empty() || t2.is_empty() { + show_text(t, render_origin(text_baseline_height / 2)); + } else { + show_text( + t1, + render_origin(-(text_baseline_height / 2 + constant::LINE_SPACE)), + ); + show_text( + t2, + render_origin(text_baseline_height + constant::LINE_SPACE * 2), + ); + } }); } ButtonContent::TextAndSubtext { @@ -393,30 +436,50 @@ impl Button { subtext, subtext_style, } => { - let base = match self.text_align { - Alignment::Start => self.area.left_center().ofs(self.content_offset), - Alignment::Center => self.area.center().ofs(self.content_offset), - Alignment::End => self.area.right_center().ofs(self.content_offset.neg()), - }; - - let text_render_origin = base - .ofs(Offset::y(self.content_height() / 2 - self.baseline_text_height()).neg()); - let subtext_render_origin = base.ofs(Offset::y(self.content_height() / 2)); - - text.map(|t| { - shape::Text::new(text_render_origin, t, stylesheet.font) - .with_fg(stylesheet.text_color) - .with_align(self.text_align) - .with_alpha(alpha) - .render(target); + let text_baseline_height = self.baseline_text_height(); + let single_line_text = text.map(|t| { + let (t1, t2) = split_two_lines( + t, + stylesheet.font, + self.area.width() - 2 * self.content_offset.x, + ); + if t1.is_empty() || t2.is_empty() { + show_text( + t, + render_origin(text_baseline_height / 2 - constant::LINE_SPACE * 2), + ); + true + } else { + show_text( + t1, + render_origin(-(text_baseline_height / 2 + constant::LINE_SPACE * 3)), + ); + show_text( + t2, + render_origin(text_baseline_height - constant::LINE_SPACE * 2), + ); + false + } }); subtext.map(|subtext| { - shape::Text::new(subtext_render_origin, subtext, subtext_style.text_font) - .with_fg(subtext_style.text_color) - .with_align(self.text_align) - .with_alpha(alpha) - .render(target); + shape::Text::new( + render_origin(if single_line_text { + text_baseline_height / 2 + + constant::LINE_SPACE + + self.baseline_subtext_height() + } else { + text_baseline_height + + constant::LINE_SPACE * 2 + + self.baseline_subtext_height() + }), + subtext, + subtext_style.text_font, + ) + .with_fg(subtext_style.text_color) + .with_align(self.text_align) + .with_alpha(alpha) + .render(target); }); } ButtonContent::Icon(icon) => { diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs index 1c555d515d..09198e1ffd 100644 --- a/core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs @@ -300,7 +300,9 @@ impl Component for VerticalMenu { // Place each button (might overflow the menu bounds) for button in self.buttons.iter_mut() { - let button_height = button.content_height() + 2 * Self::MENU_ITEM_CONTENT_PADDING; + let button_height = button + .content_height(button_width - 2 * Button::MENU_ITEM_CONTENT_OFFSET.x) + + 2 * Self::MENU_ITEM_CONTENT_PADDING; let button_bounds = Rect::from_top_left_and_size(top_left, Offset::new(button_width, button_height));