components_ui_button_IconButton.bs

import "pkg:/source/utils/misc.bs"

const focusBorderOffset = 6 ' Used for positioning calculations only (matches 6px border in 9-patch image); does NOT control actual border rendering
const textExtension = 12 ' Text extends 6px on each side to prevent label wrapping

' LAYOUT NOTE FOR DEVELOPERS:
' IconButton text extends 12px wider than the button (6px on each side) to prevent label wrapping.
' The button background/border/icon are shifted 6px to the right to center under the text.
' When positioning IconButtons, account for this 6px offset if edge alignment is important.
' Example: If aligning to x=90, the button will actually appear at x=96 (90 + 6px offset).

sub init()
  m.buttonBackground = m.top.findNode("buttonBackground")
  m.buttonBorder = m.top.findNode("buttonBorder")
  m.buttonIcon = m.top.findNode("buttonIcon")
  m.buttonIcon.ObserveField("loadStatus", "OnImageLoadStatusChanged")
  m.buttonText = m.top.findNode("buttonText")

  m.top.observeField("enabled", "onEnabledChanged")
  m.top.observeField("background", "onBackgroundChanged")
  m.top.observeField("iconBackground", "onIconBackgroundChanged")
  m.top.observeField("icon", "onIconChanged")
  m.top.observeField("text", "onTextChanged")
  m.top.observeField("height", "onHeightChanged")
  m.top.observeField("width", "onWidthChanged")
  m.top.observeField("padding", "onPaddingChanged")
  m.top.observeField("focusedChild", "onFocusChanged")
  m.top.observeField("selected", "onSelectedChanged")

  applyTheme()
end sub

sub applyTheme()
  constants = m.global.constants

  ' Set default colors
  m.top.background = constants.colorBackgroundSecondary
  m.top.focusBorder = constants.colorPrimary
  m.top.iconBackground = constants.colorTextPrimary
  m.top.focusBackground = constants.colorBackgroundSecondary
  m.top.iconFocusBackground = constants.colorTextPrimary
  m.iconSelectedColor = constants.colorSecondary
end sub

sub onFocusChanged()
  if m.top.hasFocus()
    ' Button border - change to focus color
    if m.top.enableBorder
      m.buttonBorder.blendColor = m.top.focusBorder
    else
      ' Border disabled - ensure it stays invisible
      m.buttonBorder.blendColor = m.top.background
    end if
    ' Button background
    m.buttonBackground.blendColor = m.top.focusBackground
    ' Button icon - respect selected state when focused
    if m.top.selected
      m.buttonIcon.blendColor = m.iconSelectedColor
    else
      m.buttonIcon.blendColor = m.top.iconFocusBackground
    end if
  else
    ' Button border - match background color
    m.buttonBorder.blendColor = m.top.background
    ' Button background
    m.buttonBackground.blendColor = m.top.background
    ' Button icon - respect selected state when not focused
    if m.top.selected
      m.buttonIcon.blendColor = m.iconSelectedColor
    else
      m.buttonIcon.blendColor = m.top.iconBackground
    end if
  end if
end sub

sub onBackgroundChanged()
  m.buttonBackground.blendColor = m.top.background
  ' Border matches background when not focused OR when borders are disabled
  if not m.top.hasFocus() or not m.top.enableBorder
    m.buttonBorder.blendColor = m.top.background
  end if
end sub

sub onIconBackgroundChanged()
  ' Only apply if not in selected state
  if not m.top.selected
    m.buttonIcon.blendColor = m.top.iconBackground
  end if
end sub

sub onIconChanged()
  m.buttonIcon.uri = m.top.icon
end sub

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

sub OnImageLoadStatusChanged(event as object)
  status = event.GetData()

  if isValid(status) and status = "ready"
    setIconSize()
    setFocusBorderSize()
  end if
end sub

sub setIconSize()
  ' determine size of icon
  if m.buttonIcon.bitmapWidth > 0 and m.buttonIcon.bitmapHeight > 0
    iconWidth = m.buttonIcon.bitmapWidth
    iconHeight = m.buttonIcon.bitmapHeight

    paddingPixels = 15
    m.buttonBackground.width = iconWidth + (paddingPixels * 4) ' extra horiz padding for text
    m.buttonBackground.height = iconHeight + (paddingPixels * 2)

    ' Position icon centered in button background
    buttonOffset = textExtension / 2
    m.buttonIcon.translation = [buttonOffset + ((m.buttonBackground.width - iconWidth) / 2), ((m.buttonBackground.height - iconHeight) / 2)]

    ' Update button and text positioning using shared method
    updateButtonPositioning()
  end if
end sub

sub onPaddingChanged()
  setIconSize()
end sub

sub onEnabledChanged()
  constants = m.global.constants

  if m.top.enabled
    m.top.background = constants.colorBackgroundSecondary
    m.top.iconBackground = constants.colorTextPrimary
  else
    m.top.background = constants.colorBackgroundPrimary
    m.top.iconBackground = constants.colorTextDisabled
  end if
end sub

sub onSelectedChanged()
  if m.top.selected
    ' Active/selected state - use secondary color for icon
    m.buttonIcon.blendColor = m.iconSelectedColor
  else
    ' Inactive state - restore default colors based on focus state
    if m.top.hasFocus()
      m.buttonIcon.blendColor = m.top.iconFocusBackground
    else
      m.buttonIcon.blendColor = m.top.iconBackground
    end if
  end if
end sub

' Updates button and text positioning based on textExtension constant
' This centers button elements under the wider text label
sub updateButtonPositioning()
  ' Calculate offset to center button elements under wider text
  buttonOffset = textExtension / 2

  ' Shift button elements to the right to center them under the wider text
  m.buttonBackground.translation = [buttonOffset, 0]
  m.buttonBorder.translation = [buttonOffset, 0]

  ' Text positioned at 0, extends equally on both sides
  m.buttonText.width = m.buttonBackground.width + textExtension
  m.buttonText.translation = [0, m.buttonBackground.height + (focusBorderOffset * 2)]
end sub

sub setFocusBorderSize()
  if m.buttonBackground.width < 1 then return

  ' Border poster must be same size as background
  m.buttonBorder.width = m.buttonBackground.width
  m.buttonBorder.height = m.buttonBackground.height

  ' Update button and text positioning using shared method
  updateButtonPositioning()
end sub