Repository navigation
Move Float onto views, unify Float/Dock, fill sample gaps, wire samples into CI - #59
Merged
Merged
Conversation
Restoring a floated view minted a brand-new NavigationContext with a fresh NavigationId, so a view floated again after being docked no longer matched the id its own command had captured, causing ContentRegion/ItemsRegion/TabRegion.Capture to throw. That exception propagated as an unhandled dispatcher exception and crashed the app. RegionBase.RestorePlacementAsync now carries the original NavigationId into the restored context. Also tightens the FloatingNavigationTests fixture to validate navigationId the way the real regions do (it previously ignored it, masking the bug) and adds a regression test for float -> restore -> float again.
…load Float used to be a button on each container view (MainWindow, TabRegionView, ChildContentRegionView, ItemsRegionView, ListBoxRegionView), each wired to its own Click handler and an IViewPlacementService constructor parameter. Moved it onto LightView and HeavyView themselves, mirroring how UnloadView/Dock already work - the view controls its own floating instead of a parent container doing it on its behalf. Container views and their code-behind are back to parameterless constructors with no placement wiring. Also fixes UnloadView doing nothing while a view is floating: it now closes the floating session (dispose + forget) when one exists for the view's NavigationId, falling back to the normal region-deactivate path otherwise. Applied identically to both Sample.Avalonia and Sample.Wpf.
MessageBoxIndicatorProvider.cs lives in Sample.Wpf and is plain WPF code (System.Windows.Window/Dispatcher), but was declared under namespace Sample.Avalonia - a copy/paste mistake that forced Sample.Wpf's App.xaml.cs to `using Sample.Avalonia;` just to see its own indicator provider. Sample.Avalonia/Views/TabRegionViewModel.axaml actually declares LightView (x:Class="Sample.Avalonia.Views.LightView"), colliding in name with the real, unrelated TabRegionViewModel class in Sample.Common. Renamed the file pair to LightView.axaml/.axaml.cs and updated the csproj's explicit Compile/DependentUpon entry; also deduplicated a repeated HeavyView.axaml.cs entry in the same list. Removed a dead commented-out TabbedPage.IconTemplate block left in TabbedPageView.axaml.
…igation Pipboy.Avalonia (a cosmetic Fallout-style theme, unrelated to any AsyncNavigation feature) is only published on a private GitHub Packages feed under the repo owner's personal account, which only resolves with locally-configured credentials. That made the project buildable on this machine but not on a fresh CI runner or any other contributor's machine. Swapped it for the same FluentTheme every other sample already uses, and removed the package from Directory.Packages.props. Verified the project now restores from nuget.org alone.
…and router navigation Three features the samples registered or shipped but never actually exercised anywhere: - INavigationGuard: LightViewModel now implements it via a "PreventLeave" toggle (checkbox on LightView, both platforms). Checking it blocks navigating away, mirroring the pattern from the interface's own XML doc example. - INavigationInterceptor: new SampleNavigationInterceptor logs OnNavigatingAsync/OnNavigatedAsync, registered in both Sample.Wpf and Sample.Avalonia's App startup. - Router path navigation: was already wired (RequestPathNavigateAsync) but only reachable by typing into the same free-text search box used for plain view names, with nothing distinguishing a path from a view name. Moved it out into three explicitly-labeled "Router: ..." buttons demonstrating a plain path, a multi-segment path (WithSegments), and a fallback path (WithFallback). This let MainWindowViewModel drop its IRouter dependency entirely, since nothing else used it. Blocking a navigation throws OperationCanceledException from RequestNavigateAsync/GoBack/GoForward. Command methods that are `async Task` and bound directly to a Button (AsyncNavigate, GoBack, GoForward) run through CommunityToolkit's AsyncRelayCommand, which rethrows unhandled exceptions onto the UI dispatcher - the same crash class fixed earlier for the floating NavigationId bug. Added a defensive catch around each so a blocked navigation logs instead of crashing the app, and gave AsyncNavigateAndForget/ AsyncPathNavigateAndForget's fire-and-forget continuations the same treatment for consistent, visible feedback. Added SampleCommonTests.cs covering the interceptor and the guard's CanNavigateAsync logic directly (AsyncNavigation.Tests now references Sample.Common).
…pf.InfinityNavigation These three sample projects existed but were never part of any CI job - nothing would catch them silently rotting. Added them to the existing build-samples-desktop-wasm matrix: the two Avalonia-based ones (FrontDialog, InfinityNavigation) across ubuntu/windows/macos like Sample.Avalonia.Desktop already is, and the WPF one on windows-latest only. All three were verified to restore from nuget.org alone before wiring them in (see the prior Pipboy.Avalonia removal commit). Sample.WinUI and the untracked local samples/Sampe.Wpf.InfinityNavigation build artifact are intentionally left untouched.
Previously "Dock to region" was host chrome the library drew above
the floating content (unstyled, auto-sized), while "Float" was a
sample-styled button inside the content's own button row - two
different-looking controls for opposite halves of the same action,
at two different architectural layers.
Library changes (AsyncNavigation.Floating and the Wpf/Avalonia hosts):
- FloatingWindowOptions gains ShowDockButton (default true, backward
compatible) so callers whose content provides its own dock
affordance can suppress the host's built-in button.
- IFloatingViewSession gains a StateChanged event so a caller that
floated a view can learn when it later gets docked or closed by
something other than its own command (e.g. the window's native
close button) and keep its own UI in sync.
- The Wpf/Avalonia desktop hosts' dock button also gets consistent
sizing (fixed height, centered content) for the cases where it's
still shown.
Sample changes: ViewModelBase.FloatOrDockAsync floats with
ShowDockButton = false and toggles a shared IsFloating flag (via the
new StateChanged event) that drives FloatButtonText ("Float" /
"Dock to region"). LightView/HeavyView's single Float button now IS
the dock button while floating - same style, same slot, one control
instead of two.
Added StateChanged/ShowDockButton coverage to
ViewPlacementServiceTests and a sample-level toggle test to
SampleCommonTests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Continuation of the floating-region-views work (#55, #57): relocates the Float trigger from parent containers onto the views themselves, fixes bugs the relocation surfaced, and follows up on a broader sample review (feature coverage, naming/consistency, CI coverage).
e6a15a2): mirrors how Dock already worked — the view controls its own floating instead of a parent container doing it on its behalf. Applied identically to Sample.Wpf and Sample.Avalonia.fix: preserve NavigationId across floating restore(0181e56): Float → Dock → Float again crashed, because restoring a floated view minted a newNavigationId, so a re-float with the original id no longer matched and threw an unhandled exception on the UI thread. Fixed at the sharedRegionBaselevel (covers WPF, Avalonia, and every region type at once), with a regression test.6afb456): a WPF-only file was declared undernamespace Sample.Avalonia(copy/paste mistake), and a file that actually definesLightViewwas namedTabRegionViewModel.axaml, colliding with a real unrelated class of that name.e78b8e5):Sample.InfinityNavigationreferencedPipboy.Avaloniafrom a GitHub Packages feed under a personal account, which only restored with local credentials — swapped for the sameFluentThemeevery other sample uses.91ad329):INavigationGuardandINavigationInterceptorhad zero demonstration anywhere in the repo; router/path navigation existed but was buried in the same free-text search box as plain view names. Added aPreventLeaveguard demo, a logging interceptor, and three explicitly-labeled router buttons. Also hardenedAsyncNavigate/GoBack/GoForwardagainst the same unhandled-exception class the NavigationId bug hit, since a blocked navigation now throws in the same way.Sample.FrontDialog,Sample.InfinityNavigation, andSample.Wpf.InfinityNavigationexisted but were never built by CI. Added to the build matrix (b74fbe2).feat(floating): unify Float/Dock into one view-driven toggle button(6dcf45b): Dock was unstyled host chrome above the content; Float was a sample-styled button inside it — two different-looking controls for opposite halves of one action.FloatingWindowOptions.ShowDockButton(defaulttrue, backward compatible) lets a caller suppress the host's own button, andIFloatingViewSession.StateChangedlets it learn when the session is later docked/closed by something else (e.g. the window's native close button). LightView/HeavyView's Float button now toggles in place: "Float" when docked, "Dock to region" when floating.Sample.WinUIand the fourInfinityNavigation/FrontDialogsamples' broader redundancy question were intentionally left alone (out of scope here).Test plan
dotnet test tests/AsyncNavigation.Tests— 168 passed, 2 skippeddotnet test tests/AsyncNavigation.E2E.Tests(Avalonia headless) — 9 passedINavigationGuardcheckbox blocks navigation without crashing, all three router buttons navigate correctlySample.Wpf,Sample.Avalonia.Desktop,Sample.Avalonia.Browser,Sample.FrontDialog,Sample.InfinityNavigation,Sample.Wpf.InfinityNavigation) build individuallynuget.orgalone (no reliance on locally-configured private feeds)🤖 Generated with Claude Code