Fill imperative consumption gaps: event payloads, focus, dialogs (waves P–T) - #6
Conversation
Fill the top consumption gaps found by auditing the projection against the four apps (all AXAML + view-model interop; zero imperative consumers): - Control gains KeyUp (Fields parity with KeyDown), GotFocus (NavigationMethod/KeyModifiers) and LostFocus, so keyboard and focus interaction is consumable from Rust. Control and every descendant republish under fresh IIDs. - SelectingItemsControl.SelectionChanged and TreeView.SelectionChanged now carry the added/removed item lists on host-implemented args interfaces (handler identities minted at version 2); RangeBase.ValueChanged carries the old/new value pair as fields. - avalonia-bindgen gains --write-baseline, the reviewed way to advance the frozen ABI snapshot after an intentional wave, wired into regenerate-and-build.ps1 as -UpdateAbiBaseline. - New avalonia example imperative_counter builds a full UI with no AXAML and no view-model IR, exercising composition, brushes, commands and the new event payloads end to end.
Extend the imperative surface with the pointer interactions the dogfood sample proved were missing from the event model: - Control.PointerEntered/PointerExited/Tapped now carry KeyModifiers as a Fields payload; DoubleTapped crosses as a notification (no payload members); PointerWheelChanged carries the scroll Delta as an AvnVector plus the modifiers. The host-typed members (IPointer, relative position) stay off the wire by design. - The advise slots change shape, so Control and every descendant republish under fresh IIDs (version bump via AbiVersions); the three new handler interfaces mint at version 1. The stale wave-P IIDs are recorded as retired in the sys ABI tests. - The imperative_counter example grows a wheel zone that renders the live delta, verified end to end over UI Automation.
…lling overrides
The last payload-less input event a form UI reaches for. The decimal?
old/new pair has no numeric ABI shape, so it crosses as invariant UTF-16
through the AvnDecimal converter the Value property already uses:
- EventParameterProjection gains an optional MarshallingOverride, so a
Fields payload can carry a CLR type the default extractor mapping
rejects (decimal? here) through an explicitly named host converter.
- ProjectedParameter gains StringConverterTypeName and the C# event
read/write expressions route StringUtf16 event fields through it.
- IAvnNumericUpDownValueChangedHandler republishes at version 2 with the
payload; the control itself is untouched. Rust bindgen emits
Option<String> slots natively, so no bindgen change was needed.
- imperative_counter grows a NumericUpDown whose label renders the
old/new pair; verified live over UIA ('Number: 3.2 -> 3.25').
Input focus was entirely off the projected surface, so no Rust consumer
could move focus programmatically. Focus lives on InputElement and is
inherited by every projected Control; the projection names it explicitly
and carries the navigation context:
- Control.FocusWithNavigationMethodAndKeyModifiers crosses with the
NavigationMethod and KeyModifiers enums and returns whether focus
took; Control.Focusable crosses as a Bool property.
- The new method slots republish Control and every descendant under
fresh IIDs (version bump via AbiVersions); the stale wave-Q IIDs are
recorded as retired in the sys ABI tests.
- imperative_counter focuses its text box after mount and renders the
result; verified live over UIA ('Focus: true').
Window.ShowDialog returns a Task<TResult> and never completes
synchronously, so it composes onto the shared async operation registry
the same way the storage pickers and clipboard do — exactly one
completion, cancellable, never blocking the UI thread:
- New IAvnApplication5 capability (stage 32) queried from IAvnApplication
with StartShowDialog(owner, dialog, completion); the dialog result
converts to its ABI string form on completion (null = no result).
- AvnApplication.Dialogs.cs (partial class) resolves the window tokens
up front and awaits ShowDialog; a cancellation request closes the
dialog instead of abandoning the task, because an abandoned
ShowDialog still holds the owner.
- avalonia-sys gains the dialogs module; the safe crate exposes
AppScope.show_dialog returning an AsyncOperation<Option<String>>.
- imperative_counter grows an 'Ask' button that opens a modal
accept/decline dialog and renders its result; verified live over UIA
('Dialog: accepted').
The workflow's push trigger referenced main, which is the fork's default branch; this repository's default branch is master, so the release gate never ran on upstream pushes. PRs are unaffected (the pull_request trigger has no branch filter).
CI: the "Avalonia Rust" workflow fails at startup — needs an org/repo adminEvery pull_request run of this workflow on this repo completes in 0s with What I verified:
Most likely cause: an org-level GitHub Actions policy on Until then: this PR's verification was done locally — 129 IR + 92 Generator + 375 Host managed tests green, 45 Rust suites green (including the ABI contract against the regenerated baseline), both generator |
Summary
An audit of the projection against the four apps showed every consumer uses AXAML + view-model interop and the 104-class imperative
IAvn*surface had zero consumers. This PR fills the gaps that made the imperative model unusable, in five verified waves:rust/avalonia/examples/imperative_counter.rsbuilds a full UI with no AXAML and no view-model IR. Selection events now carry AddedItems/RemovedItems on host-implemented args interfaces (handlers v2);RangeBase.ValueChangedcarries old/new fields;ControlgainsKeyUp(with settableHandled),GotFocus,LostFocus. Ships the missing wave tooling:avalonia-bindgen --write-baselineregenerates the frozenabi-baseline.json, wired asregenerate-and-build.ps1 -UpdateAbiBaseline.PointerEntered/PointerExited/Tappedcarry KeyModifiers;DoubleTappedis a notification;PointerWheelChangedcarries the scroll Delta asAvnVector.EventParameterProjection.Override+ProjectedParameter.StringConverterTypeName);NumericUpDown.ValueChangedcarries the old/new decimal pair as invariant strings through the existingAvnDecimalconverter (handler v2).Control.Focus(NavigationMethod, KeyModifiers)returning success, plusFocusable.IAvnApplication5capability withStartShowDialogriding the shared async operation registry (exactly-one completion, cancellable via dialog close, never blocks the UI thread). Safe-sideAppScope.show_dialogreturnsAsyncOperation<Option<String>>.ABI policy
All changes follow the nano-COM policy in
rust/COMPATIBILITY.md: no slot is ever inserted into a published vtable; widened interfaces republish under fresh version-bumped IIDs (AbiVersions), and stale identities are pinned as retired in the sys ABI tests so reuse fails loudly (E_NOINTERFACE) rather than calling through a stale contract. App-level capabilities are separately versionedIAvnApplicationNinterfaces (stage 32 for dialogs).Verification
+1 item(s)), wheel delta (Wheel: (0, -1)), decimal change (Number: 3.2 -> 3.25), focus on mount (Focus: true), and a modal accept/decline dialog (Dialog: accepted).--checkdeterminism gates pass;cargo fmt --checkclean; regen produces no diff.Deferred (documented)
Pointer position payloads (
GetPosition(relativeTo)is a method — needs a coordinate-space design decision),DataGrid/NativeMenuinventory, arm64 native CI execution. The dormantMarshallingKind.DialogCompletionscaffolding can be removed in a follow-up — wave T used the provenIAvnAsyncCompletionpath instead.