Patches for Observable Drag Detection / Updates - #12
Conversation
joshpar
commented
Aug 22, 2026
Thanks for your consideration! This is supporting part of a Linux port for Lyrebird ( https://realizedsound.mooo.com/josh/Lyrebird ), a synthesis and music composition environment written for Swift. |
There was a problem hiding this comment.
🟢 Approval recommended
The change is narrowly scoped to GTKViewHost.rebuild() and correctly re-establishes Observation tracking on the narrow path to avoid widget-tree teardown during active gestures.
Pull request overview
This PR fixes a GTK4 backend interaction bug where @Observable-driven updates during an in-flight drag caused GTKViewHost.rebuild() to fall back to a full widget-tree rebuild, canceling the active GtkGestureDrag and making drag updates appear to fire only once.
Changes:
- Expands the narrow in-place mutation path in
GTKViewHost.rebuild()to run for@Observable-triggered rebuilds as well (instead of forcing a full rebuild). - When the rebuild was triggered by Observation, wraps the narrow-path
describeBodypass inwithObservationTrackingso the one-shot observation subscription is re-registered without tearing down the widget subtree.
File summaries
| File | Description |
|---|---|
| Sources/Backend/GTK4/Rendering/GTKViewHost.swift | Allows Observation-triggered rebuilds to use the in-place mutation path while re-subscribing to withObservationTracking to keep updates flowing during gestures. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Summary
On the GTK4 backend, a drag whose onChanged handler writes to @observable state fires only once per press — the motion after the first drag-update is lost — so a "drag to set a value" control (knob, slider, XY pad) backed by an @observable model can't track the drag.
Root cause
GTKViewHost.rebuild() has a narrow mutation path that applies text / color / canvas changes in place (gtkCanApplyTextColorHostMutation accepts .canvasContent), preserving the widget tree — and with it any in-flight GtkGestureDrag. But that path is guarded by !fromObservation: an @Observable-driven change always falls through to a full rebuild, which tears down and recreates the subtree, cancelling the active gesture. The guard exists because the narrow path did not re-run body under withObservationTracking, which would leave the one-shot subscription dead after the first change.
Fix
Let @Observable-driven changes use the narrow path too, and re-register the subscription there: when fromObservation, run the narrow path's describe pass under withObservationTracking, so re-reading the observed properties re-subscribes — the same re-registration the full rebuild gets from buildBodyWithTracking, without the teardown. Non-narrow-applicable changes still fall through to the full rebuild. @State behaviour is unchanged.
Validation
Ubuntu 24.04 (arm64), GTK4, Swift 6.3.3. An @Observable-backed Canvas knob with .onDrag: before — one step per press; after — tracks continuously, and subsequent @observable changes still redraw (subscription stays live). A @State knob worked before and still does.
Scope
One function, GTKViewHost.rebuild(). No API change. The narrow-path guard drops !fromObservation, and the describe call is wrapped in withObservationTracking on the fromObservation branch, mirroring buildBodyWithTracking.