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