Add @State, StateBox, Binding, DependencyTracker, and SubscriptionToken for targeted reactive updates. Label gains two reactive overloads: Label(Binding<String>) for the explicit-binding path and Label(() -> String) for tracked closures. Both update the Gtk.Label text in-place without re-mounting. Regression tests verify label updates survive when the Swift wrapper is deallocated after the GObject is attached to the widget tree (GTK does not retain wrappers). Includes 28 new tests across ReactiveLabelTests and StateTests suites.
37 lines
1.1 KiB
Swift
37 lines
1.1 KiB
Swift
/// A property wrapper for reactive state owned by a ``View``.
|
|
///
|
|
/// Mutations fire targeted updates — only widgets that read this state
|
|
/// (via ``Binding`` or a tracked closure) are affected. The backing
|
|
/// ``StateBox`` is a reference type, so struct copies share the same
|
|
/// storage (``nonmutating set`` on a `let` wrapper).
|
|
///
|
|
/// ```swift
|
|
/// struct Counter: View {
|
|
/// @State private var count = 0
|
|
/// var body: some View {
|
|
/// HStack {
|
|
/// Button("-") { count -= 1 }
|
|
/// Label { "\(count)" }
|
|
/// Button("+") { count += 1 }
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
@propertyWrapper @MainActor public struct State<Value> {
|
|
private let box: StateBox<Value>
|
|
|
|
public init(wrappedValue: Value) {
|
|
box = StateBox(wrappedValue)
|
|
}
|
|
|
|
/// The current value. Reads track dependencies; writes notify.
|
|
public var wrappedValue: Value {
|
|
get { box.get() }
|
|
nonmutating set { box.set(newValue) }
|
|
}
|
|
|
|
/// A ``Binding`` to this state, for passing to child views.
|
|
public var projectedValue: Binding<Value> {
|
|
Binding(box)
|
|
}
|
|
}
|