waterui 0.3.0

A modern UI framework for Rust
import CWaterUI

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

@MainActor
class WuiAccessibilityMetadataView: PlatformView {
  let contentView: any WuiComponent

  var accessibilityTarget: PlatformView {
    var target: PlatformView = contentView
    while let metadata = target as? WuiAccessibilityMetadataView {
      target = metadata.contentView
    }
    return target
  }

  init(contentView: any WuiComponent) {
    self.contentView = contentView
    super.init(frame: .zero)
    contentView.translatesAutoresizingMaskIntoConstraints = true
    addSubview(contentView)
  }

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

  var stretchAxis: WuiStretchAxis { contentView.stretchAxis }
  func layoutPriority() -> Int32 { contentView.layoutPriority() }
  func sizeThatFits(_ proposal: WuiProposalSize) -> CGSize { contentView.sizeThatFits(proposal) }

  #if canImport(UIKit)
    override func layoutSubviews() {
      super.layoutSubviews()
      contentView.frame = bounds
    }
  #elseif canImport(AppKit)
    nonisolated override var isFlipped: Bool { true }

    override func layout() {
      super.layout()
      contentView.frame = bounds
    }
  #endif
}

@MainActor
final class WuiAccessibilityLabel: WuiAccessibilityMetadataView, WuiComponent {
  static var rawId: WuiTypeId { waterui_ignorable_metadata_accessibility_label_id() }
  private var observation: WuiComputedObservation<WuiStyledStr>?

  required init(anyview: OpaquePointer, env: WuiEnvironment) {
    let metadata = waterui_force_as_ignorable_metadata_accessibility_label(anyview)
    super.init(contentView: WuiAnyView.resolve(anyview: metadata.content, env: env))
    guard let label = metadata.label else { fatalError("accessibility label signal is null") }
    let observation = WuiComputedObservation(WuiComputed<WuiStyledStr>(label)) {
      [weak self] value, _ in self?.apply(label: value.toString())
    }
    self.observation = observation
    apply(label: observation.value.toString())
  }

  private func apply(label: String) {
    #if canImport(UIKit)
      accessibilityTarget.isAccessibilityElement = true
      accessibilityTarget.accessibilityLabel = label
    #elseif canImport(AppKit)
      accessibilityTarget.setAccessibilityElement(true)
      accessibilityTarget.setAccessibilityLabel(label)
    #endif
  }
}

@MainActor
final class WuiAccessibilityRole: WuiAccessibilityMetadataView, WuiComponent {
  static var rawId: WuiTypeId { waterui_ignorable_metadata_accessibility_role_id() }

  required init(anyview: OpaquePointer, env: WuiEnvironment) {
    let metadata = waterui_force_as_ignorable_metadata_accessibility_role(anyview)
    super.init(contentView: WuiAnyView.resolve(anyview: metadata.content, env: env))
    apply(role: metadata.value)
  }

  private func apply(role: Int32) {
    #if canImport(UIKit)
      accessibilityTarget.isAccessibilityElement = true
      let roleTraits: UIAccessibilityTraits = switch role {
      case 0, 13, 14, 15, 18, 22, 24, 25, 26: .button
      case 1: .link
      case 2: .image
      case 3, 5, 9, 12: .staticText
      case 4: .header
      case 8: .searchField
      case 16: .adjustable
      case 17: .updatesFrequently
      case 6, 7, 10, 11, 19, 20, 21, 23, 27, 28: []
      default: fatalError("unknown WaterUI accessibility role: \(role)")
      }
      var traits = accessibilityTarget.accessibilityTraits
      traits.remove([.button, .link, .image, .staticText, .header, .searchField, .adjustable,
                     .updatesFrequently])
      traits.formUnion(roleTraits)
      accessibilityTarget.accessibilityTraits = traits
    #elseif canImport(AppKit)
      accessibilityTarget.setAccessibilityElement(true)
      let accessibilityRole: NSAccessibility.Role = switch role {
      case 0: .button
      case 1: .link
      case 2: .image
      case 3: .staticText
      case 4: .group
      case 11: .list
      case 13, 24: .checkBox
      case 14, 25: .radioButton
      case 16: .slider
      case 17: .progressIndicator
      case 18: .button
      case 19: .tabGroup
      case 21: .menu
      case 22: .menuItem
      case 23: .menuBar
      case 26: .comboBox
      case 5...10, 12, 15, 20, 27, 28: .group
      default: fatalError("unknown WaterUI accessibility role: \(role)")
      }
      accessibilityTarget.setAccessibilityRole(accessibilityRole)
    #endif
  }
}

@MainActor
final class WuiAccessibilityHidden: WuiAccessibilityMetadataView, WuiComponent {
  static var rawId: WuiTypeId { waterui_ignorable_metadata_accessibility_hidden_id() }

  required init(anyview: OpaquePointer, env: WuiEnvironment) {
    let metadata = waterui_force_as_ignorable_metadata_accessibility_hidden(anyview)
    super.init(contentView: WuiAnyView.resolve(anyview: metadata.content, env: env))
    #if canImport(UIKit)
      accessibilityTarget.accessibilityElementsHidden = metadata.value != 0
    #elseif canImport(AppKit)
      accessibilityTarget.setAccessibilityElement(metadata.value == 0)
    #endif
  }
}

@MainActor
final class WuiAccessibilityChildren: WuiAccessibilityMetadataView, WuiComponent {
  static var rawId: WuiTypeId { waterui_ignorable_metadata_accessibility_children_id() }

  required init(anyview: OpaquePointer, env: WuiEnvironment) {
    let metadata = waterui_force_as_ignorable_metadata_accessibility_children(anyview)
    super.init(contentView: WuiAnyView.resolve(anyview: metadata.content, env: env))
    guard metadata.value != 0 else { return }
    #if canImport(UIKit)
      accessibilityTarget.isAccessibilityElement = true
      accessibilityTarget.accessibilityElementsHidden = true
    #elseif canImport(AppKit)
      accessibilityTarget.setAccessibilityElement(true)
      accessibilityTarget.setAccessibilityChildren([])
    #endif
  }
}

@MainActor
class WuiAccessibilityStateView: WuiAccessibilityMetadataView {
  private var observations: [AnyObject] = []
  private var disabled = false
  private var selected = false
  private var checked: Int32 = -1
  private var expanded: Int32 = -1
  private var busy = false
  #if canImport(UIKit)
    private lazy var originalAccessibilityValue = accessibilityTarget.accessibilityValue
    private lazy var originalAccessibilityHint = accessibilityTarget.accessibilityHint
    private lazy var originalAccessibilityElementsHidden = accessibilityTarget.accessibilityElementsHidden
  #elseif canImport(AppKit)
    private lazy var originalIsAccessibilityElement = accessibilityTarget.isAccessibilityElement()
  #endif

  func bind(_ state: CWaterUI.WuiAccessibilityState) {
    bind(state.disabled) { [weak self] in self?.disabled = $0 }
    bind(state.selected) { [weak self] in self?.selected = $0 }
    bind(state.checked) { [weak self] in self?.checked = $0 }
    bind(state.expanded) { [weak self] in self?.expanded = $0 }
    bind(state.busy) { [weak self] in self?.busy = $0 }
    bind(state.hidden) { [weak self] value in self?.apply(hidden: value) }
  }

  private func bind(_ pointer: OpaquePointer?, apply: @escaping (Bool) -> Void) {
    guard let pointer else { fatalError("accessibility state signal is null") }
    let observation = WuiComputedObservation(WuiComputed<Bool>(pointer)) { [weak self] value, _ in
      apply(value)
      self?.applyState()
    }
    observations.append(observation)
    apply(observation.value)
    applyState()
  }

  private func bind(_ pointer: OpaquePointer?, apply: @escaping (Int32) -> Void) {
    guard let pointer else { fatalError("accessibility state signal is null") }
    let observation = WuiComputedObservation(WuiComputed<Int32>(pointer)) { [weak self] value, _ in
      apply(value)
      self?.applyState()
    }
    observations.append(observation)
    apply(observation.value)
    applyState()
  }

  private func applyState() {
    #if canImport(UIKit)
      accessibilityTarget.isAccessibilityElement = true
      accessibilityTarget.accessibilityTraits.setValue(disabled, for: .notEnabled)
      accessibilityTarget.accessibilityTraits.setValue(selected, for: .selected)
      accessibilityTarget.accessibilityValue = switch checked {
      case 0: NSLocalizedString("Unchecked", comment: "Accessibility unchecked state")
      case 1: NSLocalizedString("Checked", comment: "Accessibility checked state")
      case 2: NSLocalizedString("Mixed", comment: "Accessibility mixed state")
      case -1: originalAccessibilityValue
      default: fatalError("unknown WaterUI accessibility checked state: \(checked)")
      }
      accessibilityTarget.accessibilityHint = busy
        ? NSLocalizedString("Busy", comment: "Accessibility busy state")
        : originalAccessibilityHint
    #elseif canImport(AppKit)
      accessibilityTarget.setAccessibilityElement(true)
      accessibilityTarget.setAccessibilityEnabled(!disabled)
      accessibilityTarget.setAccessibilitySelected(selected)
      if checked >= 0 {
        let platformValue: Int32 = switch checked {
        case 0: 0
        case 1: 1
        case 2: -1
        default: fatalError("unknown WaterUI accessibility checked state: \(checked)")
        }
        accessibilityTarget.setAccessibilityValue(platformValue)
        let valueDescription: String? = switch checked {
        case 0: NSLocalizedString("Unchecked", comment: "Accessibility unchecked state")
        case 1: NSLocalizedString("Checked", comment: "Accessibility checked state")
        case 2: NSLocalizedString("Mixed", comment: "Accessibility mixed state")
        default: nil
        }
        accessibilityTarget.setAccessibilityValueDescription(valueDescription)
      }
      switch expanded {
      case -1: break
      case 0: accessibilityTarget.setAccessibilityExpanded(false)
      case 1: accessibilityTarget.setAccessibilityExpanded(true)
      default: fatalError("unknown WaterUI accessibility expanded state: \(expanded)")
      }
      accessibilityTarget.setAccessibilityHelp(
        busy ? NSLocalizedString("Busy", comment: "Accessibility busy state") : nil
      )
    #endif
  }

  private func apply(hidden: Bool) {
    #if canImport(UIKit)
      accessibilityTarget.accessibilityElementsHidden = hidden
        ? true
        : originalAccessibilityElementsHidden
    #elseif canImport(AppKit)
      accessibilityTarget.setAccessibilityElement(hidden ? false : originalIsAccessibilityElement)
    #endif
  }
}

@MainActor
final class WuiAccessibilityState: WuiAccessibilityStateView, WuiComponent {
  static var rawId: WuiTypeId { waterui_ignorable_metadata_accessibility_state_id() }

  required init(anyview: OpaquePointer, env: WuiEnvironment) {
    let metadata = waterui_force_as_ignorable_metadata_accessibility_state(anyview)
    super.init(contentView: WuiAnyView.resolve(anyview: metadata.content, env: env))
    bind(metadata.state)
  }
}

@MainActor
final class WuiAccessibilityStateSignal: WuiAccessibilityStateView, WuiComponent {
  static var rawId: WuiTypeId { waterui_ignorable_metadata_accessibility_state_signal_id() }

  required init(anyview: OpaquePointer, env: WuiEnvironment) {
    let metadata = waterui_force_as_ignorable_metadata_accessibility_state_signal(anyview)
    super.init(contentView: WuiAnyView.resolve(anyview: metadata.content, env: env))
    bind(metadata.state)
  }
}

#if canImport(UIKit)
  private extension UIAccessibilityTraits {
    mutating func setValue(_ enabled: Bool, for trait: UIAccessibilityTraits) {
      if enabled { insert(trait) } else { remove(trait) }
    }
  }
#endif