waterui 0.3.0

A modern UI framework for Rust
// WuiSystemIcon.swift
// SystemIcon component - renders SF Symbols on Apple platforms
//
// # Layout Behavior
// SystemIcon is content-sized, using the intrinsic size of the symbol.
// The default size is based on system font body size.
//
// // INTERNAL: Layout Contract for Backend Implementers
// // - stretchAxis: .none (content-sized, does not expand)
// // - sizeThatFits: Returns intrinsic size based on symbol
// // - Priority: 0 (default)

import CWaterUI

#if canImport(UIKit)
  import UIKit
#elseif canImport(AppKit)
  import AppKit
#endif

@MainActor
final class WuiSystemIcon: PlatformView, WuiComponent {
  static var rawId: CWaterUI.WuiTypeId { waterui_system_icon_id() }

  #if canImport(UIKit)
    private let imageView = UIImageView()
  #elseif canImport(AppKit)
    private let imageView = NSImageView()
  #endif

  private let iconName: String
  private var foregroundObservation: WuiComputedObservation<WuiResolvedColor>?
  private var bodyFontObservation: WuiComputedObservation<WuiResolvedFontValue>?

  // MARK: - WuiComponent Init

  convenience init(anyview: OpaquePointer, env: WuiEnvironment) {
    let ffiIcon: CWaterUI.WuiSystemIcon = waterui_force_as_system_icon(anyview)
    let name = WuiStr(ffiIcon.name).toString()
    self.init(name: name, env: env)
  }

  // MARK: - Designated Init

  init(name: String, env: WuiEnvironment) {
    self.iconName = name
    super.init(frame: .zero)
    configureImageView()
    loadSymbol()
    foregroundObservation = WuiComputedObservation(
      themeColor: WuiColorSlot_Foreground,
      env: env
    ) { [weak self] color, _ in
      self?.applyForeground(color)
    }
    if let color = foregroundObservation?.value {
      applyForeground(color)
    }
    // SwiftUI's Image(systemName:) scales with the environment font; the
    // glyph must track the themed body font instead of freezing at the
    // symbol's default point size.
    bodyFontObservation = .bodyFont(env: env) { [weak self] font in
      self?.applySymbolFont(font)
    }
  }

  @available(*, unavailable)
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  // MARK: - Configuration

  private func configureImageView() {
    imageView.translatesAutoresizingMaskIntoConstraints = false
    addSubview(imageView)

    NSLayoutConstraint.activate([
      imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
      imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
      imageView.topAnchor.constraint(equalTo: topAnchor),
      imageView.bottomAnchor.constraint(equalTo: bottomAnchor),
    ])

    #if canImport(UIKit)
      imageView.contentMode = .scaleAspectFit
    #elseif canImport(AppKit)
      imageView.imageScaling = .scaleProportionallyUpOrDown
    #endif
  }

  private func loadSymbol() {
    #if canImport(UIKit)
      guard let image = UIImage(systemName: iconName) else {
        fatalError("Unknown SF Symbol name: \(iconName)")
      }
      imageView.image = image
    #elseif canImport(AppKit)
      guard let image = NSImage(systemSymbolName: iconName, accessibilityDescription: nil) else {
        fatalError("Unknown SF Symbol name: \(iconName)")
      }
      imageView.image = image
    #endif
  }

  private func applySymbolFont(_ font: WuiResolvedFontValue) {
    #if canImport(UIKit)
      imageView.preferredSymbolConfiguration = UIImage.SymbolConfiguration(
        font: font.toPlatformFont()
      )
    #elseif canImport(AppKit)
      let size = CGFloat(font.size)
      let configuration = NSImage.SymbolConfiguration(
        pointSize: size,
        weight: font.weight.toNSFontWeight()
      )
      guard
        let base = NSImage(systemSymbolName: iconName, accessibilityDescription: nil),
        let sized = base.withSymbolConfiguration(configuration)
      else {
        fatalError("SF Symbol \(iconName) rejected the themed font configuration")
      }
      imageView.image = sized
    #endif
    invalidateLayoutHierarchy()
  }

  private func applyForeground(_ color: WuiResolvedColor) {
    #if canImport(UIKit)
      imageView.tintColor = color.toUIColor()
    #elseif canImport(AppKit)
      imageView.contentTintColor = color.toNSColor()
    #endif
    invalidateCapturedRendering()
  }

  // MARK: - WuiComponent

  func sizeThatFits(_ proposal: WuiProposalSize) -> CGSize {
    // Default size for SF Symbols is roughly 17pt (body text size)
    // We use intrinsic content size if available
    #if canImport(UIKit)
      let intrinsic = imageView.intrinsicContentSize
      if intrinsic.width > 0 && intrinsic.height > 0 {
        return intrinsic
      }
      fatalError("SF Symbol \(iconName) has no intrinsic size")
    #elseif canImport(AppKit)
      if let image = imageView.image {
        return image.size
      }
      fatalError("SF Symbol \(iconName) has no intrinsic size")
    #endif
  }

  #if canImport(AppKit)
    nonisolated override var isFlipped: Bool { true }
  #endif
}