Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions Sources/Backend/GTK4/Rendering/GTKViewHost.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -236,19 +236,46 @@ public class GTKViewHost: AnyViewHost, DependencyTrackingHost {
observationDidFire = false
lock.unlock()

// --- Narrow mutation path: try text/color in-place update ---
// Skipped when withObservationTracking's onChange fired — the narrow
// path returns without re-running body under withObservationTracking,
// which would leave @Observable subscriptions dead after the first
// change. Fall through to the full rebuild so observation re-registers.
if !fromObservation,
let describeBody = describeBody,
// --- Narrow mutation path: try text/color/canvas in-place update ---
// Applied for both @State- and @Observable-driven changes. A full
// rebuild tears down the widget tree, which cancels any in-flight
// gesture (e.g. a drag on a Canvas knob); the narrow path mutates in
// place and preserves it. For an @Observable change the describe pass
// below is run under `withObservationTracking`, so re-reading the
// observed properties re-registers the one-shot subscription — the same
// re-registration the full rebuild gets from `buildBodyWithTracking`,
// without the teardown. If the change is not narrow-applicable we fall
// through to the full rebuild, which re-registers observation as before.
if let describeBody = describeBody,
let oldRetained = lastRetainedDescriptor,
let oldExecutor = retainedExecutor {

let previousEnv = getCurrentEnvironment()
installRebuildEnvironment()
let described = gtkDescribeCapturingCanvasPayloads(describeBody)
let described: (descriptor: GTK4DescriptorNode, canvasPayloads: [GTK4CanvasPayload])
if fromObservation {
#if canImport(Observation)
if #available(macOS 14.0, iOS 17.0, *) {
var captured: (descriptor: GTK4DescriptorNode, canvasPayloads: [GTK4CanvasPayload])!
withObservationTracking {
captured = gtkDescribeCapturingCanvasPayloads(describeBody)
} onChange: { [weak self] in
guard let self else { return }
self.lock.lock()
self.observationDidFire = true
self.lock.unlock()
self.scheduleRebuild()
}
described = captured
} else {
described = gtkDescribeCapturingCanvasPayloads(describeBody)
}
#else
described = gtkDescribeCapturingCanvasPayloads(describeBody)
#endif
} else {
described = gtkDescribeCapturingCanvasPayloads(describeBody)
}
setCurrentEnvironment(previousEnv)

let newIdentified = gtkIdentifyDescriptorTree(described.descriptor)
Expand Down