Fix remaining track-length clip (2px), add a blinking progress marker
Track length was still clipping on the right even with the hard-clip Box from the last fix -- the 3.9px/char estimate is an average, and real strings apparently render a little wider than that specifically on the right side. Flat -2px correction to the gap, per request, rather than re-deriving per-character widths. Progress bar gets a small (2px) marker at the filled/empty boundary that blinks on/off (~500ms per state) to mark "here's where playback actually is right now", not just the static split. Built the same way the glitch jitter/scanlines already handle "hold a state for N ticks in a render.Sequence" (a no-op animation.Transformation per state, not repeating a widget reference -- see _glitch_hold's own comment for why that distinction matters), but as its own small helper sized to the marker's own width/height rather than reusing _glitch_hold directly, since that function's width/height are hardcoded to ART_SLOT*scale. Marker position is clamped so it can't poke outside the bar at 0% or 100% progress. Blinks via Stack-ing an opaque/transparent pair, not two opaque colors, so it reads as a clean on/off against whichever of accent/bar_empty it happens to sit on. Verified: marker blinks correctly (sampled frames 0/5 on, 10/15 off, 20 back on) and positions correctly at a forced 60% fraction (sits right at the pink/dark boundary). Payload impact negligible -- re-checked against the same adversarial busy album art used earlier this session: 315KB, consistent with the pre-marker baseline (309-316KB).
This commit is contained in:
1 parent
cffc5edac1
commit
03a4c55d81
1 file changed
+66
-2
+66
-2
@@ -377,7 +377,12 @@ def main(config):
|
||||
if scale == 2 and progress != None:
|
||||
bpm_width = int(len(list(now["bpm"].codepoints())) * 3.9)
|
||||
length_width = int(len(list(now["track_length"].codepoints())) * 3.9)
|
||||
gap = max(0, text_width - bpm_width - length_width)
|
||||
# -2px: still clipping on the right at the plain estimate, even
|
||||
# with the hard-clip Box -- the 3.9px/char figure is an average,
|
||||
# and real strings apparently render a little wider than that on
|
||||
# the right side specifically. A flat 2px correction rather than
|
||||
# re-deriving per-character widths, per request.
|
||||
gap = max(0, text_width - bpm_width - length_width - 2)
|
||||
|
||||
# Wrapped in a Box(width=text_width, height=6) to hard-clip it --
|
||||
# every other line in this column is a Marquee, which enforces its
|
||||
@@ -1025,6 +1030,50 @@ def _message_screen(lines, colors):
|
||||
children = [render.Text(line, font = "tom-thumb", color = colors["text"]) for line in lines],
|
||||
)
|
||||
|
||||
PROGRESS_MARKER_BLINK_TICKS = 10 # ~500ms per on/off state at the default 50ms/tick
|
||||
PROGRESS_MARKER_BLINK_CYCLES = 20 # ~20s total -- covers the longest other animation in this file (GLITCH_ACTIVE_SECONDS=18s) with room to spare
|
||||
|
||||
def _progress_marker(width, height, color):
|
||||
# Blinks on/off at the filled/empty boundary -- a playhead marker for
|
||||
# "here's where playback actually is right now", not just the static
|
||||
# filled-vs-empty split. Built as a render.Sequence alternating an
|
||||
# opaque Box with a fully transparent one (not alternating between
|
||||
# two opaque colors), so it reads as a clean on/off blink regardless
|
||||
# of what's underneath, via Stack rather than needing to match
|
||||
# whichever of accent/bar_empty the marker happens to sit on.
|
||||
#
|
||||
# render.Sequence doesn't loop on its own (same fact this file
|
||||
# already leans on for the glitch jitter/scanlines), so the on/off
|
||||
# pattern is spelled out for PROGRESS_MARKER_BLINK_CYCLES repeats
|
||||
# rather than relying on any runtime looping -- 20s was picked to
|
||||
# comfortably cover this file's other longest-running animation
|
||||
# (the glitch window) rather than visibly going static mid-render.
|
||||
def _hold(child, duration):
|
||||
# Same no-op-Transformation pattern as _glitch_hold (see its own
|
||||
# comment for why this is needed, not just repeating a child
|
||||
# widget N times in the Sequence's children list), but sized to
|
||||
# this marker's own small width/height rather than _glitch_hold's
|
||||
# hardcoded ART_SLOT*scale -- that function isn't generically
|
||||
# reusable for arbitrary-sized content.
|
||||
return animation.Transformation(
|
||||
child = child,
|
||||
duration = duration,
|
||||
width = width,
|
||||
height = height,
|
||||
keyframes = [
|
||||
animation.Keyframe(percentage = 0.0, transforms = []),
|
||||
animation.Keyframe(percentage = 1.0, transforms = []),
|
||||
],
|
||||
)
|
||||
|
||||
on_state = _hold(render.Box(width = width, height = height, color = color), PROGRESS_MARKER_BLINK_TICKS)
|
||||
off_state = _hold(render.Box(width = width, height = height, color = "#00000000"), PROGRESS_MARKER_BLINK_TICKS)
|
||||
children = []
|
||||
for _ in range(PROGRESS_MARKER_BLINK_CYCLES):
|
||||
children.append(on_state)
|
||||
children.append(off_state)
|
||||
return render.Sequence(children = children)
|
||||
|
||||
def _progress_bar(fraction, colors, width = 32, height = 2):
|
||||
filled = int(width * fraction + 0.5)
|
||||
filled = max(0, min(filled, width))
|
||||
@@ -1033,7 +1082,22 @@ def _progress_bar(fraction, colors, width = 32, height = 2):
|
||||
children.append(render.Box(width = filled, height = height, color = colors["accent"]))
|
||||
if filled < width:
|
||||
children.append(render.Box(width = width - filled, height = height, color = colors["bar_empty"]))
|
||||
return render.Row(children = children)
|
||||
bar = render.Row(children = children)
|
||||
|
||||
# Marker is centered on the fill boundary, clamped so it never pokes
|
||||
# outside the bar's own width (e.g. at 0% or 100% progress, where a
|
||||
# naively-centered marker would hang half off one edge).
|
||||
marker_width = min(2, width)
|
||||
marker_x = max(0, min(filled - marker_width // 2, width - marker_width))
|
||||
marker = render.Padding(
|
||||
pad = (marker_x, 0, width - marker_x - marker_width, 0),
|
||||
child = _progress_marker(marker_width, height, colors["text"]),
|
||||
)
|
||||
|
||||
return render.Stack(children = [
|
||||
render.Box(width = width, height = height, child = bar),
|
||||
render.Box(width = width, height = height, child = marker),
|
||||
])
|
||||
|
||||
def _fetch_now_playing(station_url):
|
||||
rep = http.get(station_url + "/api/now-playing", ttl_seconds = NOWPLAYING_TTL)
|
||||
|
||||