components_ui_dropdown_TrackDropdownRow.bs

' TrackDropdownRow: Menu row used inside TrackDropdown's open-menu viewport.
'
' Mirrors JRDropdownItem structurally (Group + 9-patch bg/border + label) but swaps
' the label for a ScrollingLabelPrimarySmaller so long track names can marquee when
' the row is focused. Height-centers the label manually since ScrollingLabel lacks
' a vertAlign field.
import "pkg:/source/utils/misc.bs"

const ROW_H_PADDING = 12

sub init()
  m.itemBackground = m.top.findNode("itemBackground")
  m.itemBorder = m.top.findNode("itemBorder")
  m.itemLabel = m.top.findNode("itemLabel")

  ' Default visual state: unfocused, not selected
  m.itemLabel.color = m.global.constants.colorTextPrimary
  m.itemLabel.repeatCount = 0

  onSizeChanged()
end sub

sub onTextChanged()
  m.itemLabel.text = m.top.text
end sub

sub onSizeChanged()
  itemWidth = m.top.itemWidth
  itemHeight = m.top.itemHeight

  m.itemBackground.width = itemWidth
  m.itemBackground.height = itemHeight
  m.itemBorder.width = itemWidth
  m.itemBorder.height = itemHeight

  ' ScrollingLabel uses maxWidth as its viewport (not width). It renders a single
  ' line of text at its origin, so we offset translation.y to vertically center
  ' the text within the row (fontSizeSmaller is the label's font em-height).
  constants = m.global.constants
  fontHeight = constants.fontSizeSmaller
  labelY = int((itemHeight - fontHeight) / 2)
  m.itemLabel.maxWidth = itemWidth - (ROW_H_PADDING * 2)
  m.itemLabel.translation = [ROW_H_PADDING, labelY]
end sub

' Updates visual state based on virtual isFocused / isSelected fields. Focus chrome
' (border + background) mirrors JRDropdownItem's button pattern. repeatCount flips
' to -1 when focused so long labels marquee; 0 otherwise keeps them static.
'
' Label color is driven by isSelected ALONE — selected rows render dim (colorText
' Secondary) regardless of focus state. The focus border + filled background
' communicate "you're hovering this row"; the dim text communicates "this is your
' current pick". Both signals coexist so the user can distinguish "focused on my
' current choice" (dim text + border = OK is a no-op) from "focused on an
' alternative" (white text + border = OK switches to this).
sub onFocusStateChanged()
  constants = m.global.constants

  m.itemBorder.visible = m.top.isFocused
  m.itemBackground.visible = m.top.isFocused
  if m.top.isFocused
    m.itemBorder.blendColor = constants.colorPrimary
    m.itemBackground.blendColor = constants.colorBackgroundSecondary
    m.itemLabel.repeatCount = -1
  else
    m.itemLabel.repeatCount = 0
  end if

  if m.top.isSelected
    m.itemLabel.color = constants.colorTextSecondary
  else
    m.itemLabel.color = constants.colorTextPrimary
  end if
end sub