feat(core/caesar): use Marquee in Homescreen label
What changed, and why it matters
This commit is a user-interface polish change for the Trezor hardware wallet. It replaces a static text label on the device homescreen with a scrolling 'marquee' label so that longer text (such as a wallet name) can be read even if it does not fit in the available space. There is no security-relevant change here.
No security action required; treat as a normal UI feature commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch swaps the homescreen’s centered Label component for a Marquee component in the Caesar UI layout. It exposes Marquee::text() so the homescreen can compute text width for background-bar sizing, adjusts placement to preserve the original baseline position, and starts the marquee animation on Attach events. No cryptographic, input-validation, memory-safety, or access-control code is modified.
Changed components
core/embed/rust/src/ui/component/marquee.rscore/embed/rust/src/ui/layout_caesar/component/homescreen.rsInspect captured patch +44 / −5
### core/embed/rust/src/ui/component/marquee.rs
@@ -63,6 +63,10 @@ impl Marquee {
self.aligned_offset = self.compute_aligned_offset();
}
+ pub fn text(&self) -> TString<'static> {
+ self.text
+ }
+
pub fn start(&mut self, ctx: &mut EventCtx, now: Instant) {
// Not starting if animations are disabled.
if animation_disabled() {
### core/embed/rust/src/ui/layout_caesar/component/homescreen.rs
@@ -1,3 +1,5 @@
+use sys::time::Instant;
+
use super::super::{constant, fonts};
use super::{
theme, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos, CancelConfirmMsg,
@@ -7,7 +9,7 @@ use crate::io::BinaryData;
use crate::strutil::TString;
use crate::translations::TR;
use crate::trezorhal::usb::usb_configured;
-use crate::ui::component::{Child, Component, Event, EventCtx, Label};
+use crate::ui::component::{Child, Component, Event, EventCtx, Label, Marquee};
use crate::ui::constant::{HEIGHT, WIDTH};
use crate::ui::display::image::{ImageInfo, ToifFormat};
use crate::ui::display::{Font, Icon};
@@ -51,7 +53,7 @@ enum CurrentScreen {
pub struct Homescreen {
// TODO label should be a Child in theory, but the homescreen image is not, so it is
// always painted, so we need to always paint the label too
- label: Label<'static>,
+ label: Marquee,
notification: Option<Notification>,
custom_image: Option<BinaryData<'static>>,
/// Used for HTC functionality to lock device from homescreen
@@ -77,7 +79,8 @@ impl Homescreen {
loader_description.map(|desc| Child::new(ProgressLoader::new(desc, HOLD_TO_LOCK_MS)));
Self {
- label: Label::centered(label, theme::TEXT_BIG),
+ label: Marquee::new(label, fonts::FONT_BIG, theme::FG, theme::BG)
+ .with_alignment(Alignment::Center),
notification,
custom_image: get_homescreen_image(),
invisible_buttons: Child::new(ButtonController::new(invisible_btn_layout)),
@@ -147,7 +150,28 @@ impl Homescreen {
// the margin at top is bigger (caused by text-height vs line-height?)
// compensate by shrinking the outset
outset.top -= 5;
- shape::Bar::new(self.label.text_area().outset(outset))
+
+ let font = fonts::FONT_BIG;
+ let text_width = self.label.text().map(|t| font.text_width(t));
+ // Baseline and height as `Label::text_area()` used to compute them, so
+ // that the bar keeps its exact position.
+ let baseline_y = LABEL_AREA.y0 + font.text_max_height() - font.text_baseline();
+ // When the label fits, the background hugs the (centered) text; when it
+ // does not, the text scrolls through the whole area.
+ let text_area = if text_width <= LABEL_AREA.width() {
+ let x = LABEL_AREA.x0 + (LABEL_AREA.width() - text_width) / 2;
+ Rect::from_bottom_left_and_size(
+ Point::new(x, baseline_y),
+ Offset::new(text_width, font.text_height()),
+ )
+ } else {
+ Rect::from_bottom_left_and_size(
+ Point::new(LABEL_AREA.x0, baseline_y),
+ Offset::new(LABEL_AREA.width(), font.text_height()),
+ )
+ };
+
+ shape::Bar::new(text_area.outset(outset))
.with_bg(theme::BG)
.render(target);
@@ -165,14 +189,25 @@ impl Component for Homescreen {
type Msg = ();
fn place(&mut self, bounds: Rect) -> Rect {
- self.label.place(LABEL_AREA);
+ // Marquee draws the text baseline at `text_height - 1` from the top of
+ // its area, `Label` did at `text_max_height - text_baseline`. Shifting
+ // the area up to keep the label at its original position.
+ let font = fonts::FONT_BIG;
+ let shift = font.text_height() - 1 - (font.text_max_height() - font.text_baseline());
+ self.label.place(LABEL_AREA.translate(Offset::y(-shift)));
self.loader.place(AREA);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
Self::event_usb(self, ctx, event);
+ if let Event::Attach(_) = event {
+ self.label.start(ctx, Instant::now());
+ } else {
+ let _ = self.label.event(ctx, event);
+ }
+
// Only care about button and loader events when there is a possibility of
// locking the device
if let Some(self_loader) = &mut self.loader {Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.