Skip to content

Move Float onto views, unify Float/Dock, fill sample gaps, wire samples into CI - #59

Merged
NeverMorewd merged 7 commits into
mainfrom
sample/float-button-on-view
Oct 10, 2026
Merged

NeverMorewd merged 7 commits into
mainfrom
sample/float-button-on-view

Conversation

@NeverMorewd

Copy link
Copy Markdown
Owner

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).

  • Float button lives on the view, not the container (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 new NavigationId, so a re-float with the original id no longer matched and threw an unhandled exception on the UI thread. Fixed at the shared RegionBase level (covers WPF, Avalonia, and every region type at once), with a regression test.
  • Namespace/filename bug fixes (6afb456): a WPF-only file was declared under namespace Sample.Avalonia (copy/paste mistake), and a file that actually defines LightView was named TabRegionViewModel.axaml, colliding with a real unrelated class of that name.
  • Dropped a private-feed package dependency (e78b8e5): Sample.InfinityNavigation referenced Pipboy.Avalonia from a GitHub Packages feed under a personal account, which only restored with local credentials — swapped for the same FluentTheme every other sample uses.
  • Filled sample gaps (91ad329): INavigationGuard and INavigationInterceptor had 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 a PreventLeave guard demo, a logging interceptor, and three explicitly-labeled router buttons. Also hardened AsyncNavigate/GoBack/GoForward against the same unhandled-exception class the NavigationId bug hit, since a blocked navigation now throws in the same way.
  • CI: Sample.FrontDialog, Sample.InfinityNavigation, and Sample.Wpf.InfinityNavigation existed 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 (default true, backward compatible) lets a caller suppress the host's own button, and IFloatingViewSession.StateChanged lets 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.WinUI and the four InfinityNavigation/FrontDialog samples' broader redundancy question were intentionally left alone (out of scope here).

Test plan

  • dotnet test tests/AsyncNavigation.Tests — 168 passed, 2 skipped
  • dotnet test tests/AsyncNavigation.E2E.Tests (Avalonia headless) — 9 passed
  • Manually verified on WPF and Avalonia Desktop: Float → Dock → Float (repeated cycles, no crash), Unload while floating closes rather than no-oping, INavigationGuard checkbox blocks navigation without crashing, all three router buttons navigate correctly
  • All touched/added sample projects (Sample.Wpf, Sample.Avalonia.Desktop, Sample.Avalonia.Browser, Sample.FrontDialog, Sample.InfinityNavigation, Sample.Wpf.InfinityNavigation) build individually
  • Verified the three newly-CI'd samples restore from nuget.org alone (no reliance on locally-configured private feeds)

🤖 Generated with Claude Code

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.
@NeverMorewd
NeverMorewd merged commit ff0acbe into main Oct 10, 2026
23 checks passed
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