waterui 0.3.0

A modern UI framework for Rust
// WuiText.swift
// Styled text component - uses WuiTextBase for shared functionality

import CWaterUI

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

@MainActor
final class WuiText: WuiTextBase, WuiComponent {
  static var rawId: CWaterUI.WuiTypeId { waterui_text_id() }

  private var content: WuiComputed<WuiStyledStr>
  /// The text as its live signal, for chrome that shows this text through a
  /// platform object (a navigation bar title) instead of this view.
  var semanticContent: WuiComputed<WuiStyledStr> { content }
  private var paragraphAlignment: WuiComputed<WuiHorizontalAlignment>
  private var env: WuiEnvironment
  private var contentWatcher: WatcherGuard?
  private var alignmentWatcher: WatcherGuard?
  private var styledRenderer: WuiStyledStrRenderer?
  var onIntrinsicContentChange: (() -> Void)?

  // MARK: - WuiComponent Init

  convenience init(anyview: OpaquePointer, env: WuiEnvironment) {
    let ffiText: CWaterUI.WuiText = waterui_force_as_text(anyview)
    let content = WuiComputed<WuiStyledStr>(ffiText.content)
    let paragraphAlignment = WuiComputed<WuiHorizontalAlignment>(ffiText.paragraph_alignment)
    self.init(content: content, paragraphAlignment: paragraphAlignment, env: env)
    applyLineLimit(Int(ffiText.line_limit))
  }

  // MARK: - Designated Init

  init(
    content: WuiComputed<WuiStyledStr>, paragraphAlignment: WuiComputed<WuiHorizontalAlignment>,
    env: WuiEnvironment
  ) {
    self.content = content
    self.paragraphAlignment = paragraphAlignment
    self.env = env
    #if canImport(AppKit)
      super.init(initialText: "")
    #else
      super.init(frame: .zero)
    #endif

    startWatching()
    setParagraphAlignment(paragraphAlignment.value)
    applyText(content.value)
  }

  // MARK: - WuiComponent

  override func sizeThatFits(_ proposal: WuiProposalSize) -> CGSize {
    super.sizeThatFits(proposal)
  }

  // MARK: - Reactive Updates

  private func startWatching() {
    contentWatcher = content.watch { [weak self] value, metadata in
      guard let self else { return }
      #if canImport(UIKit)
        withCrossDissolveAnimation(self.label, metadata) {
          self.applyText(value)
        }
      #elseif canImport(AppKit)
        withCrossDissolveAnimation(self.textField, metadata) {
          self.applyText(value)
        }
      #endif
    }

    alignmentWatcher = paragraphAlignment.watch { [weak self] value, _ in
      guard let self else { return }
      self.setParagraphAlignment(value)
    }
  }

  private func applyText(_ styled: WuiStyledStr) {
    styledRenderer = WuiStyledStrRenderer(styled: styled, env: env) { [weak self] in
      self?.applyResolvedText()
    }
    applyResolvedText()
  }

  private func applyResolvedText() {
    guard let styledRenderer else { return }
    setAttributedText(styledRenderer.attributedString())
    onIntrinsicContentChange?()
  }
}