Skip to content

toolkit: Native — a host-filled control region (the anti-Foreign) - #438

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

toolkit: Native — a host-filled control region (the anti-Foreign)#438
tannevaled merged 1 commit into
mainfrom
feat/native

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

⚠ Draft — do not merge until its consumer lands

`Native` is the toolkit half of the native AppKit widget framework. It must land with its consumer — the `go-widgets/window` cocoa `syncNative` host loop (PR to follow) — never before it, because a placement seam with no caller is exactly what got the old `Foreign` retired in #428. Kept as a draft so it is not auto-merged while consumer-less.

What this adds

A `Native` widget: a rectangle the toolkit lays out but does not paint, so a host that can embed real platform controls places one of its own there — a real `NSSecureTextField` for a password, the system slider, etc. Backed by the new `go-macos/appkit` binding on macOS.

Why it is not a revival of Foreign

Foreign carried a rect, a key and an opaque payload, and died for want of a consumer a rect could serve. A real control differs in two ways a bare region cannot express, and both are the reasons its consumer couldn't adopt Foreign:

  • The value flows back. The person edits the control, so each value is a two-way `mvvm.Observable` (`Text`/`On`/`Number`), `eq`-guarded so the binding does not loop.
  • It persists across frames. A control keeps focus and selection, so the host finds it again by a stable `Key` and reconciles rather than rebuilding.

`WalkNative` reuses Foreign's sound clip-aware descent verbatim. `Draw`/`OnEvent` stand down once claimed; `Children` exposes the fallback (for a11y/layout) only while unclaimed, so a claimed control's accessibility is never doubled.

Tests

`native.go` is 100% covered by `native_test.go`, using fake walk-interface implementations that pin the clip math deterministically (no ScrollView gutter arithmetic). Full toolkit suite green locally; gofmt/vet clean.

… a real control

A Native is a rectangle the toolkit lays out but does not paint: a host that can
embed platform controls places one of its own where a Native sits, so a password
box is the operating system's secure field, a slider is the system slider — the
behaviour a drawn imitation cannot carry (secure text entry, the exact focus
ring, native accessibility).

This is deliberately NOT the old Foreign, retired in #428 for want of a consumer
a bare rectangle could serve. A real control is not a picture: the person edits
it, so its value must flow BACK to the model, and it survives across frames, so
the host must find the same control again rather than rebuild it and lose focus.
Native answers both — a mvvm.Observable per value for two-way binding, and a
stable Key the host diffs on — which is what earns it the place Foreign could not
hold. Its consumer is the go-widgets/window cocoa backend, landing alongside; it
is not merged before that caller exists.

WalkNative reuses Foreign's sound clip-aware descent (childOffsetter /
childContainer / childClipper), returning each placement with the clip an
enclosing viewport imposes. Draw/OnEvent stand down once a host claims the
region; Children exposes the fallback (for a11y and layout) only while unclaimed,
so the native control's own accessibility is never doubled. native.go is 100%
covered by native_test.go through fake walk-interface implementations that pin
the clip math without a ScrollView's gutter arithmetic.
@tannevaled
tannevaled marked this pull request as ready for review August 31, 2026 09:55
@tannevaled
tannevaled merged commit bb8c330 into main Aug 31, 2026
1 check passed
@tannevaled
tannevaled deleted the feat/native branch August 31, 2026 09:55
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