components_ui_poster_JRPoster.bs
import "pkg:/source/utils/misc.bs"
' VideoProgressBar's own height: the strip the bar occupies along the poster's bottom edge.
const PROGRESS_BAR_STRIP = 9
' Gap between the versions badge and the bar. Without it the badge's bottom edge IS the bar's
' top edge, and the two read as one welded element on a partly-watched tile.
const VERSIONS_BADGE_GAP = 6
sub init()
m.top.width = 464
m.top.height = 261
m.top.loadDisplayMode = "scaleToZoom"
m.disableWatchBadge = false
if m.global.user.settings.uiTvShowsDisableUnwatchedCount
m.disableWatchBadge = true
end if
m.watchBadgeBackground = m.top.findNode("watchBadgeBackground")
m.unplayedVideosCount = m.top.findNode("unplayedVideosCount")
m.checkmark = m.top.findNode("checkmark")
m.versionsBadgeBackground = m.top.findNode("versionsBadgeBackground")
' Theme-coloured glyph on a dark scrim. colorSecondary is the constant for a NON-focusable
' highlight (components/CLAUDE.md), which is what a badge is, so the badge re-themes with
' the rest of the app instead of being pinned to white. The scrim carries the legibility:
' at alpha85 the composite is #262626 even over a white poster, so what is underneath stops
' mattering and the glyph reads against a near-constant backdrop.
m.versionsBadgeBackground.color = m.global.constants.colorBlack + m.global.constants.alpha85
m.top.findNode("versionsIcon").blendColor = m.global.constants.colorSecondary
' Observed once for the life of the poster. A recycled cell's resetBadge() must not
' re-render while it clears, so it raises this instead of toggling the observers.
m.isResettingBadge = false
m.top.observeField("playedPercentage", "onPlayedPercentageChanged")
' Not gated on uiTvShowsDisableUnwatchedCount: that setting hides the WATCHED badge, and
' widening it to mean "all poster badges" would break what its name and docs promise.
m.top.observeField("mediaSourceCount", "onMediaSourceCountChanged")
if not m.disableWatchBadge
m.top.observeField("unplayedCount", "onUnplayedCountChanged")
m.top.observeField("isWatched", "onIsWatchedChanged")
end if
end sub
sub onUnplayedCountChanged()
if m.isResettingBadge or m.disableWatchBadge then return
if m.top.isWatched then return
if m.top.unplayedCount > 0
m.checkmark.visible = false
m.unplayedVideosCount.text = m.top.unplayedCount.ToStr()
m.unplayedVideosCount.visible = true
else
m.unplayedVideosCount.visible = false
m.unplayedVideosCount.text = ""
m.checkmark.visible = true
end if
updateBadgeSize()
end sub
sub onIsWatchedChanged()
if m.isResettingBadge or m.disableWatchBadge then return
if m.top.unplayedCount > 0 then return
if m.top.isWatched
m.unplayedVideosCount.visible = false
m.unplayedVideosCount.text = ""
m.checkmark.visible = true
else
m.checkmark.visible = false
end if
updateBadgeSize()
end sub
' Marks a tile whose item has more than one version, so the choice is visible while browsing
' instead of only on the details screen.
'
' Pinned to the BOTTOM-LEFT, clear of the progress bar's strip plus a gap. The bar is added
' and removed on demand, so this badge cannot depend on whether it is there; the offset is
' computed from the constants, not from a bar node that may not exist yet.
'
' Still flush to the LEFT edge, matching the watched badge's flush top-right anchoring. If the
' badge system later adopts a uniform inset, both move together (see
' `poster-badge-system-undesigned`).
sub onMediaSourceCountChanged()
if m.isResettingBadge then return
if m.top.mediaSourceCount > 1
m.versionsBadgeBackground.translation = [0, m.top.height - m.versionsBadgeBackground.height - PROGRESS_BAR_STRIP - VERSIONS_BADGE_GAP]
m.versionsBadgeBackground.visible = true
else
m.versionsBadgeBackground.visible = false
end if
end sub
sub onPlayedPercentageChanged()
if m.isResettingBadge then return
progressBar = m.top.findNode("progressBar")
if not isValid(progressBar)
if m.top.playedPercentage = 0
return
else
progressBar = createProgressBar()
end if
end if
if m.top.playedPercentage = 0
m.top.removeChild(progressBar)
else if m.top.playedPercentage > 0
progressBar.width = m.top.width
progressBar.playedPercentage = m.top.playedPercentage
m.top.appendChild(progressBar)
end if
end sub
sub resetBadge()
' Handlers run synchronously on these writes, so the flag only has to span them.
m.isResettingBadge = true
m.top.unplayedCount = 0
m.top.isWatched = false
m.top.playedPercentage = 0
m.top.mediaSourceCount = 0
m.isResettingBadge = false
m.watchBadgeBackground.visible = false
m.versionsBadgeBackground.visible = false
m.unplayedVideosCount.text = ""
m.checkmark.visible = false
m.watchBadgeBackground.width = 90
m.checkmark.translation = [0, 0]
m.unplayedVideosCount.translation = [0, 0]
progressBar = m.top.findNode("progressBar")
if isValid(progressBar)
m.top.removeChild(progressBar)
end if
end sub
sub updateBadgeSize()
padding = 12
if m.top.isWatched
padding = 15
dataWidth = m.checkmark.localBoundingRect().width
m.checkmark.translation = [padding, 3]
else
textLength = len(m.unplayedVideosCount.text)
fontSize = m.unplayedVideosCount.font.size
charWidth = fontSize * 0.64
dataWidth = textLength * charWidth
if dataWidth < 27 then dataWidth = 27
m.unplayedVideosCount.width = dataWidth
m.unplayedVideosCount.translation = [padding, 3]
end if
badgeWidth = dataWidth + (padding * 2)
m.watchBadgeBackground.width = badgeWidth
m.watchBadgeBackground.translation = [m.top.width - badgeWidth, 0]
m.watchBadgeBackground.visible = true
end sub
function createProgressBar() as object
progressBar = CreateObject("roSGNode", "VideoProgressBar")
progressBar.id = "progressBar"
progressBar.width = m.top.width
progressBar.translation = [0, m.top.height - progressBar.height]
return progressBar
end function