portico/Sources/Portico/State/WidgetRef.swift

43 lines
2 KiB
Swift

import Gtk
/// A live, order-independent reference to a widget produced elsewhere in the view tree.
///
/// Declare a reference with `@WidgetRef`, publish a producing view with ``View/ref(_:)``,
/// and pass the projected binding to any modifier that accepts the referenced widget.
/// The reference is initially `nil`; publishing another widget replaces the current value.
/// When the publishing subtree unmounts, the reference is cleared only if it still points
/// at that subtree's widget.
@propertyWrapper @MainActor public struct WidgetRef<W: Gtk.Widget> {
private let box = StateBox<W?>(nil)
/// Creates an unset widget reference.
public init() {}
/// The referenced widget, or `nil` until the view carrying ``View/ref(_:)`` mounts.
/// Reads track, so a tracked closure that reads this re-runs when the reference changes.
public var wrappedValue: W? { box.get() }
/// The read-only binding consumed by reference-taking widget modifiers.
/// Writes are ignored; publishing views own the reference value.
public var projectedValue: Binding<W?> { Binding(readOnly: box) }
/// Publishes a mounted widget and clears it when that publishing subtree tears down.
///
/// The widget is checked dynamically because Swift cannot express the relationship
/// between the publisher's target type and this reference's generic type. Multiple
/// publishers are allowed; the latest publisher wins, and an older publisher cannot
/// clear a newer value during teardown.
@_spi(Portico) public func _publish(_ widget: Gtk.Widget, registry: NodeRegistry) {
guard let typed = widget as? W else {
preconditionFailure(
"WidgetRef<\(W.self)>: .ref() applied to a view whose widget is \(type(of: widget)). Declare the ref at that type, or at a superclass of it."
)
}
box.set(typed)
registry.add(SubscriptionToken { [box] in
if box.peek() === typed {
box.set(nil)
}
})
}
}