fix(core): don't request anim frames if text fits
What changed, and why it matters
This commit is a small UI cleanup for Trezor hardware wallets. It stops on-screen scrolling text (marquees) from requesting animation frames when the text already fits in its allotted space. This saves battery and avoids unnecessary screen redraws, but it does not fix a security vulnerability.
No security action required. Treat as a normal UI/performance fix.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch moves the ‘does text fit?’ check into the Marquee component itself. Previously, callers such as Title and Button tracked a separate flag (needs_marquee / subtext_is_marquee) to decide whether to start or render the marquee. Now Marquee::start() detects a non-negative max_offset and skips animation, and callers simply always call start(). The title.rs file removes the needs_marquee field, and button.rs only instantiates a Marquee when subtext_is_marquee is true. The change is a performance/rendering correctness fix, not a memory-safety or cryptographic bug fix.
Changed components
core/embed/rust/src/ui/component/marquee.rscore/embed/rust/src/ui/layout_caesar/component/title.rscore/embed/rust/src/ui/layout_eckhart/component/button.rsInspect captured patch +27 / −25
diff --git a/core/embed/rust/src/ui/component/marquee.rs b/core/embed/rust/src/ui/component/marquee.rs
index d4c9f8c2..30d2b981 100644
--- a/core/embed/rust/src/ui/component/marquee.rs
+++ b/core/embed/rust/src/ui/component/marquee.rs
@@ -70,6 +70,11 @@ impl Marquee {
self.min_offset = 0;
self.max_offset = max_offset;
+ // If text fits completely, don't start animation
+ if max_offset >= 0 {
+ return;
+ }
+
let anim = Animation::new(self.min_offset, max_offset, self.duration, now);
self.state = State::Left(anim);
@@ -141,8 +146,9 @@ impl Component for Marquee {
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
- // Not doing anything if animations are disabled.
- if animation_disabled() {
+ // Not doing anything if animations are disabled or if we never started (text
+ // fits)
+ if animation_disabled() || matches!(self.state, State::Initial) {
return None;
}
diff --git a/core/embed/rust/src/ui/layout_caesar/component/title.rs b/core/embed/rust/src/ui/layout_caesar/component/title.rs
index 344712a6..9cbfaf1e 100644
--- a/core/embed/rust/src/ui/layout_caesar/component/title.rs
+++ b/core/embed/rust/src/ui/layout_caesar/component/title.rs
@@ -15,7 +15,6 @@ pub struct Title {
area: Rect,
title: TString<'static>,
marquee: Marquee,
- needs_marquee: bool,
centered: bool,
}
@@ -24,7 +23,6 @@ impl Title {
Self {
title,
marquee: Marquee::new(title, theme::FONT_HEADER, theme::FG, theme::BG),
- needs_marquee: false,
area: Rect::zero(),
centered: false,
}
@@ -42,13 +40,8 @@ impl Title {
pub fn set_text(&mut self, ctx: &mut EventCtx, new_text: TString<'static>) {
self.title = new_text;
self.marquee.set_text(new_text);
- let text_width = new_text.map(|s| theme::FONT_HEADER.text_width(s));
- self.needs_marquee = text_width > self.area.width();
- // Resetting the marquee to the beginning and starting it when necessary.
self.marquee.reset();
- if self.needs_marquee {
- self.marquee.start(ctx, Instant::now());
- }
+ self.marquee.start(ctx, Instant::now());
}
pub fn height() -> i16 {
@@ -91,24 +84,20 @@ impl Component for Title {
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
self.marquee.place(bounds);
- let width = self.title.map(|s| theme::FONT_HEADER.text_width(s));
- self.needs_marquee = width > self.area.width();
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
- if self.needs_marquee {
- if matches!(event, Event::Attach(_)) {
- self.marquee.start(ctx, Instant::now());
- } else {
- return self.marquee.event(ctx, event);
- }
+ if matches!(event, Event::Attach(_)) {
+ self.marquee.start(ctx, Instant::now());
+ } else {
+ return self.marquee.event(ctx, event);
}
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
- if self.needs_marquee {
+ if self.title.map(|s| theme::FONT_HEADER.text_width(s)) > self.area.width() {
self.marquee.render(target);
} else if self.centered {
Self::render_header_centered(target, &self.title, self.area);
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 524511c2..a67c762d 100644
--- a/core/embed/rust/src/ui/layout_eckhart/component/button.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/component/button.rs
@@ -65,13 +65,20 @@ impl Button {
ButtonContent::TextAndSubtext {
subtext,
subtext_style,
+ subtext_is_marquee,
..
- } => Some(Marquee::new(
- subtext,
- subtext_style.text_font,
- subtext_style.text_color,
- subtext_style.background_color,
- )),
+ } => {
+ if subtext_is_marquee {
+ Some(Marquee::new(
+ subtext,
+ subtext_style.text_font,
+ subtext_style.text_color,
+ subtext_style.background_color,
+ ))
+ } else {
+ None
+ }
+ }
_ => None,
};
Self {
Why this scored 18/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.