Skip to content

cocoa: reconcile NativeControl descriptors (Surface provider or widget tree) - #88

Merged
tannevaled merged 1 commit into
mainfrom
feat/native-descriptor
Aug 31, 2026
Merged

cocoa: reconcile NativeControl descriptors (Surface provider or widget tree)#88
tannevaled merged 1 commit into
mainfrom
feat/native-descriptor

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Reworks the cocoa native-control backend from #86 onto go-widgets/toolkit's new NativeControl descriptor (toolkit v0.299.0), so it serves both an ordinary widget tree and a self-rendering `toolkit.Surface` — which is how the news reader is built (its Surface has no walkable widget tree, so `WalkNative` alone could not reach its controls).

What changes

  • `syncNative` now gathers this frame's controls from the root's own provider (`interface{ NativeControls() []toolkit.NativeControl }`, a Surface) when present, else by `WalkNative`, and reconciles the same flat descriptors either way — one real AppKit control per `Key`, held across frames.
  • Immediate-mode-safe binding: a descriptor's value is pushed into a control only when it differs from what the control last reported. A person's edit flows out through the callback and the app's next descriptor carries that same value (equal → no push → the caret is never disturbed); only an app-side change differs and is pushed. This replaces cocoa: back toolkit.Native with embedded AppKit controls (syncNative) #86's observable subscription, which couldn't serve a Surface and pushed on every set.

Proof

Two gated live tests (`WINDOW_COCOA_INTEGRATION`, run on-device): the widget-tree path and the Surface-provider path (the reader's mechanism), each embedding a real `NSSecureTextField`, round-tripping its value, and pushing an app-side change into the field. The `gatherNative` provider-vs-walk selection is unit-tested in CI.

Deps

Requires `go-widgets/toolkit` v0.299.0 and `go-macos/appkit` v0.1.0.

…t tree)

Reworks syncNative onto go-widgets/toolkit's NativeControl descriptor
(toolkit v0.299.0). The backend gathers this frame's controls from the root's
own provider when it has one — a self-rendering toolkit.Surface, which is how the
news reader is built — else by walking it as a widget tree, and reconciles the
same descriptors either way, holding one real AppKit control per Key across
frames.

The value binding is immediate-mode-safe by construction: a descriptor's value is
pushed into a control only when it differs from what the control last reported.
When the person edits, the change flows out through the descriptor's callback and
the app's next descriptor carries that same value — equal, so nothing is pushed
back and the caret is never disturbed. Only a value the app changed on its own is
pushed. This replaces the observable-subscription binding of #86, which could not
serve a Surface app and pushed on every observable set rather than per frame.

Proven on-device by two gated live tests (WINDOW_COCOA_INTEGRATION): the
widget-tree path and the Surface-provider path — the reader's actual mechanism —
each embedding a real NSSecureTextField, round-tripping its value, and pushing an
app-side change into the field.

Requires go-widgets/toolkit v0.299.0 and go-macos/appkit v0.1.0.
@tannevaled
tannevaled merged commit 1db82af into main Aug 31, 2026
27 checks passed
@tannevaled
tannevaled deleted the feat/native-descriptor branch August 31, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant