Skip to content

feat: add floating region views for WPF and Avalonia - #55

Merged
NeverMorewd merged 14 commits into
mainfrom
codex/region-floating
Sep 29, 2026
Merged

NeverMorewd merged 14 commits into
mainfrom
codex/region-floating

Conversation

@NeverMorewd

@NeverMorewd NeverMorewd commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

Add opt-in floating region views for WPF and Avalonia. A rendered view can move into a separate window and dock back without creating another view instance. Window close ends the floating session; a separate Dock to region button restores it.

  • Navigating to a cached floating instance activates its existing window and leaves the main region unchanged. New instances can still open independently.
  • Docking into an occupied ContentRegion switches to the original instance and updates navigation state and history. Tab and item regions retain their other entries.
  • Coordinate navigation, docking, and closing, including navigation guards, cancellation, and attachment rollback. Pin floating instances in the cache and remove closed instances without affecting another same-name view.
  • Add sample entry points and visual-state, lifecycle, and navigation regression tests for both UI frameworks.

Compatibility

Floating support requires explicit registration. Restore the public NavigationAwareBase with its original API and default behavior, marked obsolete with a recommendation to implement INavigationAware directly. Update the English and Chinese README examples accordingly.

Clear non-floating cache entries before invoking disposal so a throwing Dispose cannot leave unrelated old entries cached. Floating views remain protected. Existing IPlatformService and IRegionManager interface changes already on main are outside this PR.

Validation

  • Core test suite: 155 passed, 2 skipped.
  • WPF end-to-end suite: 8 passed; Avalonia end-to-end suite: 7 passed.
  • Compatibility/cache tests rerun after the final test cleanup: 20 passed.
  • WPF and Avalonia samples and floating libraries build successfully; core also builds for net8.0.
  • A consumer compiled against the v2.0.2 source runs after replacing its Core/WPF DLLs with the updated versions, including a NavigationAwareBase subclass. The disposal-failure probe confirms old cache entries no longer survive Clear.
  • git diff --check passes.

Sample builds report existing NU1507 package-source warnings. The full multi-platform solution was not built.

Mobile CI

Explicitly install Android platform-tools instead of the setup action's obsolete tools default. Raise the iOS sample's minimum supported OS from 13 to 15 in both the project and Info.plist to match the CI workload. These changes affect sample/CI configuration, not library target frameworks.

NeverMorewd and others added 14 commits September 1, 2026 07:48
- Make ViewPlacementCoordinator.EnterAsync reentrant per logical async
  call chain to fix a deadlock when a navigation callback (e.g.
  OnNavigatedToAsync) triggers a placement change on the same region;
  exposes IsHeld() for lease-contract enforcement.
- Make WeakUnloadObserver.Subscribe idempotent so docking a floated
  view back no longer double-subscribes its unload handler.
- Respect RegionPlacementItem.WasSelected on Attach instead of forcing
  activation, and only correct selection on a failed restore when it
  was actually disturbed.
- Set up the indicator host unconditionally at the start of
  CreateNavigateTask so a failed or externally-activated navigation
  always has a usable IndicatorHost.
- Add the missing Selected ?? Items.LastOrDefault() fallback to
  TabRegion/TabbedPageRegion.Capture() to match ItemsRegion behavior.
- Fail fast in GetContentHost when an indicator host doesn't support
  content detachment, instead of silently corrupting the visual tree.
- Enforce the documented placement-lease contract in
  RegionBase.RestorePlacementAsync via ViewPlacementCoordinator.IsHeld.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AvaloniaFloatingWindowHostFactory previously required Avalonia's classic
desktop lifetime and threw NotSupportedException otherwise, so FloatAsync
was unusable on browser/WASM and mobile single-view apps. It now selects
the host per ApplicationLifetime: classic desktop still gets a real OS
Window, while single-view apps get a new AvaloniaOverlayFloatingWindowHost
that renders the floating content as a draggable, resizable in-page
overlay panel (via Avalonia's OverlayLayer) with the same Dock/Close
contract as the native window host.

Verified with a new headless E2E test and manually in Sample.Avalonia.Browser
via dotnet run + Chrome: float, drag, dock, and close all round-trip
correctly with no console errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MainWindow.axaml previously hosted its own "Floating: current MainRegion
view" button + description around <views:MainView />, so the demo only
existed on classic desktop; MainView (the shared root used directly by
Avalonia's single-view lifetime on browser/WASM, Android and iOS) never
had it. Move the button and its FloatMainRegion_Click handler into
MainView itself, and have MainWindow construct MainView in code and
forward its IViewPlacementService instead of duplicating the header.
Desktop, browser, Android and iOS now show the identical set of floating
samples (MainRegion, TabRegion, ChildContentRegion, ItemsRegion,
ListBoxRegion), with desktop's existing behavior as the reference.

Verified in Sample.Avalonia.Browser via dotnet run + Chrome: the
MainRegion float/dock button now shows on the home page and round-trips
correctly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AsyncNavigation.E2E.Tests (Avalonia.Headless.XUnit) already exercises
floating/docking/visual-state behavior but was never wired into CI - only
tests/AsyncNavigation.Tests ran, and the sample jobs just build, never
test. Add a test-e2e job mirroring the existing test job's 3-OS matrix.

Retarget the test project from net10.0-windows to net10.0: nothing in it
uses a Windows-only API, and Avalonia.Headless is cross-platform by
design, so the Windows-only TFM was an unnecessary constraint blocking
Linux/macOS runs. Verified locally (Windows) in both Debug and Release.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@NeverMorewd
NeverMorewd merged commit 27cd0f0 into main Sep 29, 2026
16 checks passed
NeverMorewd added a commit that referenced this pull request Oct 10, 2026
…#58)

Fixes 5 of the 6 previously-deferred, pre-existing (not introduced by the
earlier floating-feature fixes) review findings; the 6th (ViewManager.AddView's
UpdateDuplicateKey path overwriting a still-floating view's cache slot) turned
out to be intended, tested behavior once verified against
CloseOldInstance_DoesNotRemoveNewInstanceCacheOrHistory, so it was left as-is.

- RegionNavigationService.OnAfterNavigationAsync: call OnNavigatedFromAsync
  before the pipeline's cancellation check throws, if cancellation raced in
  right after OnNavigatedToAsync succeeded - otherwise the view's lifecycle
  state (activated, never told it left) goes out of sync with the region
  reverting. Covered by a new regression test that reproduces the race via a
  test double's activation hook.
- WeakUnloadObserver.Subscribe: take Func<INavigationAware, Task> instead of
  Action<INavigationAware> and await it, so its one caller's async lambda is a
  real awaited continuation instead of async-void with unobservable exceptions.
- ViewManager: Remove(key, dispose) now returns bool (true if it actually
  removed something) instead of silently no-op'ing when the entry is pinned by
  a floating view, so callers can tell the two cases apart.
- ViewManager.TrimCache: resume scanning from where the previous eviction left
  off instead of restarting from the LRU tail every iteration, avoiding
  O(evictions * pinned-views) work when floating views sit at the tail.
- Extracted the "cancel the window's close, then raise CloseRequested" state
  machine - duplicated near-verbatim between AvaloniaFloatingWindowHost and
  WpfFloatingWindowHost - into a shared FloatingWindowCloseGuard in
  AsyncNavigation.Floating, so this exact subtle dance only needs fixing once.

Verified: dotnet test tests/AsyncNavigation.Tests (158 passed) and
tests/AsyncNavigation.E2E.Tests (9 passed), Debug and Release, plus a full
solution build - all green.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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