fix(ui): marquee's hold no longer dominates a barely-overflowing label

User report: a repeater path popup's short "?A1B2C3D4"-style hop tags
(often just a couple pixels over the box width) felt stuck rather than
scrolling, while longer names (Nodes list) already felt right after the
recent marqueeHoldMs() speedup.

Root cause: the swing marquee's hold applies uniformly regardless of how
much text is actually hidden. When only 1-2 codepoints overflow, the full
cycle is hold + a single, barely-there step + hold + step back -- nearly
all hold, for almost no new information revealed each side. New
marqueeHoldForSkip(skip_cp) scales the hold down (to 1/3 or 1/2) when the
computed reveal distance is small; longer overflows keep the full hold,
since there's real text worth pausing to read.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-08 11:12:23 +02:00
co-authored by Claude Sonnet 5
parent 57e029ccc4
commit e300db7908
+17 -3
View File
@@ -348,6 +348,20 @@ public:
virtual unsigned long marqueeHoldMs() { return isEink() ? 1200 : 700; } virtual unsigned long marqueeHoldMs() { return isEink() ? 1200 : 700; }
virtual uint8_t marqueeStepChars() { return isEink() ? 3 : 1; } virtual uint8_t marqueeStepChars() { return isEink() ? 3 : 1; }
// Scales the start/end hold down when only a codepoint or two is actually
// hidden -- with a fixed hold, a label that overflows by just that much
// (e.g. a short "?A1B2C3D4" hex hop tag squeezed by a couple of pixels)
// spends nearly its whole cycle sitting still either side of a single,
// barely-there step, which reads as "stuck" rather than "scrolling" for
// how little extra text it actually reveals. Longer overflows keep the
// full hold, since there's real content worth pausing to read.
unsigned long marqueeHoldForSkip(uint16_t skip_cp) {
unsigned long full = marqueeHoldMs();
if (skip_cp <= 2) return full / 3;
if (skip_cp <= 4) return full / 2;
return full;
}
// draw text with ellipsis if it exceeds max_width. Pass selected=true for // draw text with ellipsis if it exceeds max_width. Pass selected=true for
// the row currently highlighted/focused by the user: instead of a static // the row currently highlighted/focused by the user: instead of a static
// ellipsis, an overflowing label then animates a "swing" marquee — holds at // ellipsis, an overflowing label then animates a "swing" marquee — holds at
@@ -417,7 +431,6 @@ public:
_marquee_max_w = max_width; _marquee_max_w = max_width;
_marquee_skip_cp = 0; _marquee_skip_cp = 0;
_marquee_phase = 0; // hold at start _marquee_phase = 0; // hold at start
_marquee_next_at = now + marqueeHoldMs();
// Find the codepoint skip at which the remaining suffix's own width // Find the codepoint skip at which the remaining suffix's own width
// already fits max_width — i.e. the fully-scrolled end position. // already fits max_width — i.e. the fully-scrolled end position.
@@ -431,6 +444,7 @@ public:
if (full_width - removed_w <= max_width) break; if (full_width - removed_w <= max_width) break;
} }
_marquee_max_skip_cp = cp_count; _marquee_max_skip_cp = cp_count;
_marquee_next_at = now + marqueeHoldForSkip(cp_count);
} }
// Advance the state machine at most once per elapsed step/hold interval. // Advance the state machine at most once per elapsed step/hold interval.
@@ -444,7 +458,7 @@ public:
if (_marquee_skip_cp >= _marquee_max_skip_cp) { if (_marquee_skip_cp >= _marquee_max_skip_cp) {
_marquee_skip_cp = _marquee_max_skip_cp; _marquee_skip_cp = _marquee_max_skip_cp;
_marquee_phase = 2; _marquee_phase = 2;
_marquee_next_at = now + marqueeHoldMs(); _marquee_next_at = now + marqueeHoldForSkip(_marquee_max_skip_cp);
} else { } else {
_marquee_next_at = now + marqueeStepMs(); _marquee_next_at = now + marqueeStepMs();
} }
@@ -455,7 +469,7 @@ public:
if (_marquee_skip_cp <= step) { if (_marquee_skip_cp <= step) {
_marquee_skip_cp = 0; _marquee_skip_cp = 0;
_marquee_phase = 0; _marquee_phase = 0;
_marquee_next_at = now + marqueeHoldMs(); _marquee_next_at = now + marqueeHoldForSkip(_marquee_max_skip_cp);
} else { } else {
_marquee_skip_cp -= step; _marquee_skip_cp -= step;
_marquee_next_at = now + marqueeStepMs(); _marquee_next_at = now + marqueeStepMs();