components_ui_tabbar_JRTab.bs
sub init()
' TextButton.init() has already run: m.buttonBackground, m.buttonBorder, m.buttonText are set
constants = m.global.constants
' Upgrade font to Large to match overhang title size
m.buttonText.font.size = constants.fontSizeLarge
' Match TextButton's default padding for consistent styling
m.top.padding = 30
' Unfocused state: transparent background/border so only text is visible
m.top.background = "0x00000000"
m.top.border = "0x00000000"
m.top.textColor = constants.colorTextSecondary
' Focused state: standard TextButton appearance
m.top.focusBorder = constants.colorPrimary
m.top.focusBackground = constants.colorBackgroundSecondary
m.top.textFocusColor = constants.colorTextPrimary
' Create underline indicator programmatically (positioned after sizing in onReady)
m.indicator = createObject("roSGNode", "Rectangle")
m.indicator.height = 6
m.indicator.color = constants.colorSecondary
m.indicator.visible = false
m.top.appendChild(m.indicator)
' Position indicator after TextButton finishes sizing
m.top.observeField("isReady", "onReady")
' isButtonSelected and isFocused are declared by TextButton (parent) without onChange — observe here
m.top.observeField("isButtonSelected", "onStateChanged")
m.top.observeField("isFocused", "onStateChanged")
end sub
' Maps tabTitle to TextButton's text field, triggering TextButton's sizing flow
sub onTitleChanged()
m.top.text = m.top.tabTitle
end sub
' Positions the underline indicator inside the button after TextButton sizes itself.
' The indicator sits below the text at text width, inside the background bounds.
' When focused, the TextButton border naturally wraps around it (ResumeButton pattern).
sub onReady()
if not m.top.isReady then return
padding = m.top.padding
bgWidth = m.buttonBackground.width
bgHeight = m.buttonBackground.height
' Text sits from y=padding to y=bgHeight-padding inside the background
textWidth = bgWidth - (padding * 2)
textBottom = bgHeight - padding
m.indicator.width = textWidth
m.indicator.translation = [padding, textBottom + 12]
m.indicator.visible = m.top.isButtonSelected
end sub
' Overrides TextButton.onFocusChanged to use the virtual isFocused field.
' Individual tabs never receive real Roku focus — JRTabBar manages focus
' externally via the isFocused field.
sub onFocusChanged()
isFocused = m.top.isFocused
if isFocused
m.buttonBorder.blendColor = m.top.focusBorder
m.buttonBackground.blendColor = m.top.focusBackground
m.buttonBorder.visible = true
m.buttonBackground.visible = true
m.buttonText.color = m.top.textFocusColor
else
m.buttonBorder.visible = false
m.buttonBackground.visible = false
if m.top.isButtonSelected
m.buttonText.color = m.global.constants.colorTextPrimary
else
m.buttonText.color = m.global.constants.colorTextSecondary
end if
end if
end sub
' Called when isFocused or isButtonSelected changes — updates all visual state
sub onStateChanged()
onFocusChanged()
m.indicator.visible = m.top.isButtonSelected
end sub