Browse Source

Merge branch 'master' into bugfix/macos-unhandled-keydown-event-propagation

pull/20944/head
RaoulSeifertEnscape 4 days ago
committed by GitHub
parent
commit
f299d906bb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 216
      api/Avalonia.nupkg.xml
  2. 4
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  3. BIN
      samples/ControlCatalog/Assets/Sanctuary/city_bg.jpg
  4. BIN
      samples/ControlCatalog/Assets/Sanctuary/forest_bg.jpg
  5. BIN
      samples/ControlCatalog/Assets/Sanctuary/main_arctic_silence.jpg
  6. BIN
      samples/ControlCatalog/Assets/Sanctuary/main_deep_forest.jpg
  7. BIN
      samples/ControlCatalog/Assets/Sanctuary/main_desert_sands.jpg
  8. BIN
      samples/ControlCatalog/Assets/Sanctuary/main_hero.jpg
  9. BIN
      samples/ControlCatalog/Assets/Sanctuary/mountain_bg.jpg
  10. 9
      samples/ControlCatalog/ControlCatalog.csproj
  11. 11
      samples/ControlCatalog/MainView.xaml
  12. 11
      samples/ControlCatalog/Pages/CarouselDemoPage.xaml
  13. 91
      samples/ControlCatalog/Pages/CarouselDemoPage.xaml.cs
  14. 117
      samples/ControlCatalog/Pages/CarouselPage.xaml
  15. 116
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  16. 98
      samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml
  17. 1068
      samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml.cs
  18. 119
      samples/ControlCatalog/Pages/CarouselPage/CarouselCustomizationPage.xaml
  19. 48
      samples/ControlCatalog/Pages/CarouselPage/CarouselCustomizationPage.xaml.cs
  20. 60
      samples/ControlCatalog/Pages/CarouselPage/CarouselDataBindingPage.xaml
  21. 95
      samples/ControlCatalog/Pages/CarouselPage/CarouselDataBindingPage.xaml.cs
  22. 557
      samples/ControlCatalog/Pages/CarouselPage/CarouselGalleryAppPage.xaml
  23. 101
      samples/ControlCatalog/Pages/CarouselPage/CarouselGalleryAppPage.xaml.cs
  24. 93
      samples/ControlCatalog/Pages/CarouselPage/CarouselGesturesPage.xaml
  25. 59
      samples/ControlCatalog/Pages/CarouselPage/CarouselGesturesPage.xaml.cs
  26. 74
      samples/ControlCatalog/Pages/CarouselPage/CarouselGettingStartedPage.xaml
  27. 40
      samples/ControlCatalog/Pages/CarouselPage/CarouselGettingStartedPage.xaml.cs
  28. 140
      samples/ControlCatalog/Pages/CarouselPage/CarouselMultiItemPage.xaml
  29. 47
      samples/ControlCatalog/Pages/CarouselPage/CarouselMultiItemPage.xaml.cs
  30. 115
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageCustomizationPage.xaml
  31. 79
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageCustomizationPage.xaml.cs
  32. 44
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageDataTemplatePage.xaml
  33. 209
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageDataTemplatePage.xaml.cs
  34. 37
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageEventsPage.xaml
  35. 92
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageEventsPage.xaml.cs
  36. 61
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageFirstLookPage.xaml
  37. 35
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageFirstLookPage.xaml.cs
  38. 64
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageGesturePage.xaml
  39. 44
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageGesturePage.xaml.cs
  40. 45
      samples/ControlCatalog/Pages/CarouselPage/CarouselPagePerformancePage.xaml
  41. 103
      samples/ControlCatalog/Pages/CarouselPage/CarouselPagePerformancePage.xaml.cs
  42. 90
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageSelectionPage.xaml
  43. 56
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageSelectionPage.xaml.cs
  44. 65
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageTransitionsPage.xaml
  45. 69
      samples/ControlCatalog/Pages/CarouselPage/CarouselPageTransitionsPage.xaml.cs
  46. 97
      samples/ControlCatalog/Pages/CarouselPage/CarouselTransitionsPage.xaml
  47. 66
      samples/ControlCatalog/Pages/CarouselPage/CarouselTransitionsPage.xaml.cs
  48. 132
      samples/ControlCatalog/Pages/CarouselPage/CarouselVerticalPage.xaml
  49. 39
      samples/ControlCatalog/Pages/CarouselPage/CarouselVerticalPage.xaml.cs
  50. 298
      samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml
  51. 11
      samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml.cs
  52. 335
      samples/ControlCatalog/Pages/CarouselPage/SanctuaryShowcasePage.xaml
  53. 70
      samples/ControlCatalog/Pages/CarouselPage/SanctuaryShowcasePage.xaml.cs
  54. 95
      samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml
  55. 220
      samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml.cs
  56. 1
      samples/ControlCatalog/Pages/CommandBarPage.xaml.cs
  57. 32
      samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml
  58. 3
      samples/ControlCatalog/Pages/DrawerDemoPage.xaml.cs
  59. 113
      samples/ControlCatalog/Pages/DrawerPage/DrawerPageBreakpointPage.xaml
  60. 84
      samples/ControlCatalog/Pages/DrawerPage/DrawerPageBreakpointPage.xaml.cs
  61. 5
      samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml
  62. 13
      samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml.cs
  63. 13
      samples/ControlCatalog/Pages/DrawerPage/DrawerPageFirstLookPage.xaml.cs
  64. 8
      samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml
  65. 3
      samples/ControlCatalog/Pages/NavigationDemoPage.xaml.cs
  66. 51
      samples/ControlCatalog/Pages/NavigationPage/LAvenirAppPage.xaml.cs
  67. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageAppearancePage.xaml.cs
  68. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageAttachedMethodsPage.xaml.cs
  69. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageBackButtonPage.xaml.cs
  70. 7
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml
  71. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml.cs
  72. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageFirstLookPage.xaml.cs
  73. 19
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageGesturePage.xaml.cs
  74. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageInteractiveHeaderPage.xaml.cs
  75. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalPage.xaml.cs
  76. 8
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalTransitionsPage.xaml.cs
  77. 95
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmNavigation.cs
  78. 86
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmPage.xaml
  79. 32
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmPage.xaml.cs
  80. 252
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmPageFactory.cs
  81. 238
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmViewModels.cs
  82. 6
      samples/ControlCatalog/Pages/NavigationPage/NavigationPagePassDataPage.xaml.cs
  83. 24
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageScrollAwarePage.xaml.cs
  84. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageStackPage.xaml.cs
  85. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageTitlePage.xaml.cs
  86. 5
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageToolbarPage.xaml.cs
  87. 8
      samples/ControlCatalog/Pages/NavigationPage/NavigationPageTransitionsPage.xaml.cs
  88. 6
      samples/ControlCatalog/Pages/NavigationPage/PulseAppPage.xaml.cs
  89. 35
      samples/ControlCatalog/Pages/NavigationPage/RetroGamingAppPage.xaml
  90. 43
      samples/ControlCatalog/Pages/NavigationPage/RetroGamingAppPage.xaml.cs
  91. 7
      samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs
  92. 2
      samples/ControlCatalog/Pages/TabbedDemoPage.xaml.cs
  93. 16
      samples/ControlCatalog/Pages/TabbedPage/TabbedPageCustomTabBarPage.xaml.cs
  94. 11
      samples/ControlCatalog/Pages/TabbedPage/TabbedPageCustomizationPage.xaml.cs
  95. 13
      samples/ControlCatalog/Pages/TabbedPage/TabbedPageDataTemplatePage.xaml
  96. 157
      samples/ControlCatalog/Pages/TabbedPage/TabbedPageDataTemplatePage.xaml.cs
  97. 8
      samples/ControlCatalog/Pages/TabbedPage/TabbedPageFabPage.xaml.cs
  98. 13
      samples/ControlCatalog/Pages/TabbedPage/TabbedPageGesturePage.xaml.cs
  99. 36
      samples/ControlCatalog/Pages/TabbedPage/TabbedPageWithNavigationPage.xaml
  100. 6
      samples/ControlCatalog/Pages/TabbedPage/TabbedPageWithNavigationPage.xaml.cs

216
api/Avalonia.nupkg.xml

@ -991,6 +991,18 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.CrossAxisCancelThresholdProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.EdgeSizeProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Input.HoldingState.Cancelled</Target>
@ -1147,6 +1159,30 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.get_CrossAxisCancelThreshold</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.get_EdgeSize</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.set_CrossAxisCancelThreshold(System.Double)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.set_EdgeSize(System.Double)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.HoldingRoutedEventArgs.#ctor(Avalonia.Input.HoldingState,Avalonia.Point,Avalonia.Input.PointerType)</Target>
@ -1411,6 +1447,18 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.SwipeGestureEventArgs.#ctor(System.Int32,Avalonia.Input.SwipeDirection,Avalonia.Vector,Avalonia.Point)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.SwipeGestureEventArgs.get_StartPoint</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel</Target>
@ -1801,6 +1849,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.DrawerPage.DrawerBreakpointWidthProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.NativeMenuBar.EnableMenuItemClickForwardingProperty</Target>
@ -1813,6 +1867,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Primitives.FlyoutBase.IsOpenProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Primitives.Popup.PlacementModeProperty</Target>
@ -1843,6 +1903,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TabItem.IconProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target>
@ -1981,6 +2047,18 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.DrawerPage.get_DrawerBreakpointWidth</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.DrawerPage.set_DrawerBreakpointWidth(System.Double)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.get_Surfaces</Target>
@ -2017,6 +2095,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.PageSelectionChangedEventArgs.#ctor(Avalonia.Controls.Page,Avalonia.Controls.Page)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Platform.DefaultMenuInteractionHandler.GotFocus(System.Object,Avalonia.Input.GotFocusEventArgs)</Target>
@ -2221,6 +2305,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.VisualLayerManager.get_IsPopup</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.VisualLayerManager.get_LightDismissOverlayLayer</Target>
@ -2239,12 +2329,30 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.VisualLayerManager.set_IsPopup(System.Boolean)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Screens.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TabbedPage.FindNextEnabledTab(System.Int32,System.Int32)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TabItem.get_Icon</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TabItem.SubscribeToOwnerProperties(Avalonia.AvaloniaObject)</Target>
@ -2545,6 +2653,18 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.CrossAxisCancelThresholdProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.EdgeSizeProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Input.HoldingState.Cancelled</Target>
@ -2701,6 +2821,30 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.get_CrossAxisCancelThreshold</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.get_EdgeSize</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.set_CrossAxisCancelThreshold(System.Double)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.set_EdgeSize(System.Double)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.HoldingRoutedEventArgs.#ctor(Avalonia.Input.HoldingState,Avalonia.Point,Avalonia.Input.PointerType)</Target>
@ -2965,6 +3109,18 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.SwipeGestureEventArgs.#ctor(System.Int32,Avalonia.Input.SwipeDirection,Avalonia.Vector,Avalonia.Point)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.SwipeGestureEventArgs.get_StartPoint</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel</Target>
@ -3355,6 +3511,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.DrawerPage.DrawerBreakpointWidthProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.NativeMenuBar.EnableMenuItemClickForwardingProperty</Target>
@ -3367,6 +3529,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Primitives.FlyoutBase.IsOpenProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Primitives.Popup.PlacementModeProperty</Target>
@ -3397,6 +3565,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TabItem.IconProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target>
@ -3535,6 +3709,18 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.DrawerPage.get_DrawerBreakpointWidth</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.DrawerPage.set_DrawerBreakpointWidth(System.Double)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.get_Surfaces</Target>
@ -3571,6 +3757,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.PageSelectionChangedEventArgs.#ctor(Avalonia.Controls.Page,Avalonia.Controls.Page)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Platform.DefaultMenuInteractionHandler.GotFocus(System.Object,Avalonia.Input.GotFocusEventArgs)</Target>
@ -3775,6 +3967,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.VisualLayerManager.get_IsPopup</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.VisualLayerManager.get_LightDismissOverlayLayer</Target>
@ -3793,12 +3991,30 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.VisualLayerManager.set_IsPopup(System.Boolean)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Screens.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TabbedPage.FindNextEnabledTab(System.Int32,System.Int32)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TabItem.get_Icon</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TabItem.SubscribeToOwnerProperties(Avalonia.AvaloniaObject)</Target>

4
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -563,6 +563,10 @@ NSWindowStyleMask WindowImpl::CalculateStyleMask() {
case SystemDecorationsBorderOnly:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView;
if (_canResize && _isEnabled) {
s = s | NSWindowStyleMaskResizable;
}
break;
case SystemDecorationsFull:

BIN
samples/ControlCatalog/Assets/Sanctuary/city_bg.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

BIN
samples/ControlCatalog/Assets/Sanctuary/forest_bg.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

BIN
samples/ControlCatalog/Assets/Sanctuary/main_arctic_silence.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

BIN
samples/ControlCatalog/Assets/Sanctuary/main_deep_forest.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

BIN
samples/ControlCatalog/Assets/Sanctuary/main_desert_sands.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

BIN
samples/ControlCatalog/Assets/Sanctuary/main_hero.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

BIN
samples/ControlCatalog/Assets/Sanctuary/mountain_bg.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

9
samples/ControlCatalog/ControlCatalog.csproj

@ -11,14 +11,7 @@
<AvaloniaXaml Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaXaml>
<AvaloniaResource Include="Assets\*" />
<AvaloniaResource Include="Assets\Fonts\*" />
<AvaloniaResource Include="Assets\Restaurant\*" />
<AvaloniaResource Include="Assets\Pulse\*" />
<AvaloniaResource Include="Assets\Movies\*" />
<AvaloniaResource Include="Assets\CurvedHeader\*" />
<AvaloniaResource Include="Assets\RetroGaming\*" />
<AvaloniaResource Include="Assets\ModernApp\*" />
<AvaloniaResource Include="Assets\**\*" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\image1.jpg" />

11
samples/ControlCatalog/MainView.xaml

@ -54,8 +54,15 @@
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:CommandBarPage />
</TabItem>
<TabItem Header="Carousel">
<pages:CarouselPage />
<TabItem Header="Carousel"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:CarouselDemoPage />
</TabItem>
<TabItem Header="CarouselPage"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:CarouselDemoPage />
</TabItem>
<TabItem Header="CheckBox">

11
samples/ControlCatalog/Pages/CarouselDemoPage.xaml

@ -0,0 +1,11 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselDemoPage">
<NavigationPage x:Name="SampleNav">
<NavigationPage.Styles>
<Style Selector="NavigationPage#SampleNav /template/ Border#PART_NavigationBar">
<Setter Property="Background" Value="Transparent" />
</Style>
</NavigationPage.Styles>
</NavigationPage>
</UserControl>

91
samples/ControlCatalog/Pages/CarouselDemoPage.xaml.cs

@ -0,0 +1,91 @@
using System;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselDemoPage : UserControl
{
private static readonly (string Group, string Title, string Description, Func<UserControl> Factory)[] Demos =
{
// Overview
("Overview", "First Look",
"Basic CarouselPage with three pages and page indicator.",
() => new CarouselPageFirstLookPage()),
// Populate
("Populate", "Data Templates",
"Bind CarouselPage to an ObservableCollection, add or remove pages at runtime, and switch the page template.",
() => new CarouselPageDataTemplatePage()),
// Appearance
("Appearance", "Customization",
"Switch slide direction between horizontal and vertical with PageSlide. Page indicator dots update on each selection.",
() => new CarouselPageCustomizationPage()),
// Features
("Features", "Page Transitions",
"Animate page switches with CrossFade or PageSlide.",
() => new CarouselPageTransitionsPage()),
("Features", "Programmatic Selection",
"Jump to any page programmatically with SelectedIndex and respond to SelectionChanged events.",
() => new CarouselPageSelectionPage()),
("Features", "Gesture & Keyboard",
"Swipe left/right to navigate pages. Toggle IsGestureEnabled and IsKeyboardNavigationEnabled.",
() => new CarouselPageGesturePage()),
("Features", "Events",
"SelectionChanged, NavigatedTo, and NavigatedFrom events. Swipe or navigate to see the live event log.",
() => new CarouselPageEventsPage()),
// Performance
("Performance", "Performance Monitor",
"Track page count, live page instances, and managed heap size. Observe how GC reclaims memory after removing pages.",
() => new CarouselPagePerformancePage()),
// Showcases
("Showcases", "Sanctuary",
"Travel discovery app with 3 full-screen immersive pages. Each page has a real background photo, gradient overlay, and themed content. Built as a 1:1 replica of a Stitch design.",
() => new SanctuaryShowcasePage()),
("Showcases", "Care Companion",
"Healthcare onboarding with CarouselPage (3 pages), then a TabbedPage patient dashboard. Skip or complete onboarding to navigate to the dashboard via RemovePage.",
() => new CareCompanionAppPage()),
// Carousel (ItemsControl) demos
("Carousel", "Getting Started",
"Basic Carousel with image items and previous/next navigation buttons.",
() => new CarouselGettingStartedPage()),
("Carousel", "Transitions",
"Configure page transitions: PageSlide, CrossFade, 3D Rotation, or None.",
() => new CarouselTransitionsPage()),
("Carousel", "Customization",
"Adjust orientation and transition type to tailor the carousel layout.",
() => new CarouselCustomizationPage()),
("Carousel", "Gestures & Keyboard",
"Navigate items via swipe gesture and arrow keys. Toggle each input mode on and off.",
() => new CarouselGesturesPage()),
("Carousel", "Vertical Orientation",
"Carousel with Orientation set to Vertical, navigated with Up/Down keys, swipe, or buttons.",
() => new CarouselVerticalPage()),
("Carousel", "Multi-Item Peek",
"Adjust ViewportFraction to show multiple items simultaneously with adjacent cards peeking.",
() => new CarouselMultiItemPage()),
("Carousel", "Data Binding",
"Bind Carousel to an ObservableCollection and add, remove, or shuffle items at runtime.",
() => new CarouselDataBindingPage()),
("Carousel", "Curated Gallery",
"Editorial art gallery app with DrawerPage navigation, hero Carousel with PipsPager dots, and a horizontal peek carousel for collection highlights.",
() => new CarouselGalleryAppPage()),
};
public CarouselDemoPage()
{
InitializeComponent();
Loaded += OnLoaded;
}
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
await SampleNav.PushAsync(NavigationDemoHelper.CreateGalleryHomePage(SampleNav, Demos), null);
}
}
}

117
samples/ControlCatalog/Pages/CarouselPage.xaml

@ -1,44 +1,117 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">An items control that displays its items as pages that fill the control.</TextBlock>
<StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Stretch">
<TextBlock Classes="h2">A swipeable items control that can reveal adjacent pages with ViewportFraction.</TextBlock>
<Grid ColumnDefinitions="Auto,*,Auto"
MaxWidth="660"
<Grid Name="layoutGrid" ColumnDefinitions="Auto,*,Auto" RowDefinitions="*,Auto,*"
MaxWidth="760"
HorizontalAlignment="Stretch" Margin="0 16 0 0">
<Button Name="left" Grid.Column="0" VerticalAlignment="Center" Padding="10,20" Margin="4">
<Path Data="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" Fill="Black"/>
<Button Name="left" Grid.Column="0" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" Padding="10,20" Margin="4">
<Path Name="leftArrow" Data="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" Fill="Black"/>
</Button>
<Carousel Name="carousel" Grid.Column="1">
<Carousel Name="carousel" Grid.Column="1" Grid.Row="1" Background="Transparent" Height="400" Focusable="True" ViewportFraction="1.0">
<Carousel.PageTransition>
<PageSlide Duration="0.25" Orientation="Horizontal" />
</Carousel.PageTransition>
<Image Source="/Assets/delicate-arch-896885_640.jpg"/>
<Image Source="/Assets/hirsch-899118_640.jpg"/>
<Image Source="/Assets/maple-leaf-888807_640.jpg"/>
<Border Margin="14,12" CornerRadius="18" ClipToBounds="True">
<Grid>
<Image Source="/Assets/delicate-arch-896885_640.jpg" Stretch="UniformToFill"/>
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 1: Delicate Arch" Foreground="White" HorizontalAlignment="Center" FontWeight="SemiBold"/>
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="18" ClipToBounds="True">
<Grid>
<Image Source="/Assets/hirsch-899118_640.jpg" Stretch="UniformToFill"/>
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 2: Hirsch" Foreground="White" HorizontalAlignment="Center" FontWeight="SemiBold"/>
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="18" ClipToBounds="True">
<Grid>
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill"/>
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 3: Maple Leaf" Foreground="White" HorizontalAlignment="Center" FontWeight="SemiBold"/>
</Border>
</Grid>
</Border>
</Carousel>
<Button Name="right" Grid.Column="2" VerticalAlignment="Center" Padding="10,20" Margin="4">
<Path Data="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" Fill="Black"/>
<Button Name="right" Grid.Column="2" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" Padding="10,20" Margin="4">
<Path Name="rightArrow" Data="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" Fill="Black"/>
</Button>
</Grid>
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock VerticalAlignment="Center">Transition</TextBlock>
<ComboBox Name="transition" SelectedIndex="1" VerticalAlignment="Center">
<Separator Margin="0 4"/>
<Grid ColumnDefinitions="160,420" RowDefinitions="Auto, Auto, Auto" RowSpacing="8"
Margin="0 4" MaxWidth="580" HorizontalAlignment="Left">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center">Transition</TextBlock>
<ComboBox SelectedIndex="1" Name="transition" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Slide</ComboBoxItem>
<ComboBoxItem>Crossfade</ComboBoxItem>
<ComboBoxItem>3D Rotation</ComboBoxItem>
<ComboBoxItem>Page Slide</ComboBoxItem>
<ComboBoxItem>Cross Fade</ComboBoxItem>
<ComboBoxItem>Rotate 3D</ComboBoxItem>
<ComboBoxItem>Card Stack</ComboBoxItem>
<ComboBoxItem>Wave Reveal</ComboBoxItem>
<ComboBoxItem>Composite (Slide + Fade)</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock VerticalAlignment="Center">Orientation</TextBlock>
<ComboBox Name="orientation" SelectedIndex="0" VerticalAlignment="Center">
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center">Orientation</TextBlock>
<ComboBox Name="orientation" Grid.Row="1" Grid.Column="1" SelectedIndex="0" HorizontalAlignment="Stretch">
<ComboBoxItem>Horizontal</ComboBoxItem>
<ComboBoxItem>Vertical</ComboBoxItem>
</ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center">Viewport Fraction</TextBlock>
<StackPanel Grid.Row="2" Grid.Column="1" Spacing="4" HorizontalAlignment="Stretch">
<Grid ColumnDefinitions="*,56" ColumnSpacing="12">
<Slider Name="viewportFraction"
Minimum="0.33"
Maximum="1"
Value="1.0"
TickFrequency="0.01"
HorizontalAlignment="Stretch" />
<TextBlock Name="viewportFractionIndicator"
Grid.Column="1"
HorizontalAlignment="Stretch"
TextAlignment="Right"
VerticalAlignment="Center"
FontWeight="SemiBold">1.00</TextBlock>
</Grid>
<TextBlock Name="viewportFractionHint"
HorizontalAlignment="Stretch"
Opacity="0.75"
TextWrapping="Wrap"
Text="Values below 1 reveal adjacent pages." />
</StackPanel>
</Grid>
<Separator Margin="0 8"/>
<StackPanel Orientation="Horizontal" Spacing="24" Margin="0 4" MaxWidth="580" HorizontalAlignment="Left">
<CheckBox Name="wrapSelection">Wrap Selection</CheckBox>
<CheckBox Name="swipeEnabled">Swipe Enabled</CheckBox>
</StackPanel>
<Separator Margin="0 8"/>
<StackPanel Spacing="12" MaxWidth="580" HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal" Spacing="24">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock>Total Items:</TextBlock>
<TextBlock Name="itemsCountIndicator" FontWeight="Bold">0</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock>Selected Index:</TextBlock>
<TextBlock Name="selectedIndexIndicator" FontWeight="Bold">0</TextBlock>
</StackPanel>
</StackPanel>
</StackPanel>
</StackPanel>

116
samples/ControlCatalog/Pages/CarouselPage.xaml.cs

@ -1,6 +1,9 @@
using System;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using ControlCatalog.Pages.Transitions;
namespace ControlCatalog.Pages
{
@ -9,28 +12,137 @@ namespace ControlCatalog.Pages
public CarouselPage()
{
InitializeComponent();
left.Click += (s, e) => carousel.Previous();
right.Click += (s, e) => carousel.Next();
transition.SelectionChanged += TransitionChanged;
orientation.SelectionChanged += TransitionChanged;
viewportFraction.ValueChanged += ViewportFractionChanged;
wrapSelection.IsChecked = carousel.WrapSelection;
wrapSelection.IsCheckedChanged += (s, e) =>
{
carousel.WrapSelection = wrapSelection.IsChecked ?? false;
UpdateButtonState();
};
swipeEnabled.IsChecked = carousel.IsSwipeEnabled;
swipeEnabled.IsCheckedChanged += (s, e) =>
{
carousel.IsSwipeEnabled = swipeEnabled.IsChecked ?? false;
};
carousel.PropertyChanged += (s, e) =>
{
if (e.Property == SelectingItemsControl.SelectedIndexProperty)
{
UpdateButtonState();
}
else if (e.Property == Carousel.ViewportFractionProperty)
{
UpdateViewportFractionDisplay();
}
};
carousel.ViewportFraction = viewportFraction.Value;
UpdateButtonState();
UpdateViewportFractionDisplay();
}
private void UpdateButtonState()
{
itemsCountIndicator.Text = carousel.ItemCount.ToString();
selectedIndexIndicator.Text = carousel.SelectedIndex.ToString();
var wrap = carousel.WrapSelection;
left.IsEnabled = wrap || carousel.SelectedIndex > 0;
right.IsEnabled = wrap || carousel.SelectedIndex < carousel.ItemCount - 1;
}
private void ViewportFractionChanged(object? sender, RangeBaseValueChangedEventArgs e)
{
carousel.ViewportFraction = Math.Round(e.NewValue, 2);
UpdateViewportFractionDisplay();
}
private void UpdateViewportFractionDisplay()
{
var value = carousel.ViewportFraction;
viewportFractionIndicator.Text = value.ToString("0.00");
var pagesInView = 1d / value;
viewportFractionHint.Text = value >= 1d
? "1.00 shows a single full page."
: $"{pagesInView:0.##} pages fit in view. Try 0.80 for peeking or 0.33 for three full items.";
}
private void TransitionChanged(object? sender, SelectionChangedEventArgs e)
{
var isVertical = orientation.SelectedIndex == 1;
var axis = isVertical ? PageSlide.SlideAxis.Vertical : PageSlide.SlideAxis.Horizontal;
switch (transition.SelectedIndex)
{
case 0:
carousel.PageTransition = null;
break;
case 1:
carousel.PageTransition = new PageSlide(TimeSpan.FromSeconds(0.25), orientation.SelectedIndex == 0 ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical);
carousel.PageTransition = new PageSlide(TimeSpan.FromSeconds(0.25), axis);
break;
case 2:
carousel.PageTransition = new CrossFade(TimeSpan.FromSeconds(0.25));
break;
case 3:
carousel.PageTransition = new Rotate3DTransition(TimeSpan.FromSeconds(0.5), orientation.SelectedIndex == 0 ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical);
carousel.PageTransition = new Rotate3DTransition(TimeSpan.FromSeconds(0.5), axis);
break;
case 4:
carousel.PageTransition = new CardStackPageTransition(TimeSpan.FromSeconds(0.5), axis);
break;
case 5:
carousel.PageTransition = new WaveRevealPageTransition(TimeSpan.FromSeconds(0.8), axis);
break;
case 6:
carousel.PageTransition = new CompositePageTransition
{
PageTransitions =
{
new PageSlide(TimeSpan.FromSeconds(0.25), axis),
new CrossFade(TimeSpan.FromSeconds(0.25)),
}
};
break;
}
UpdateLayoutForOrientation(isVertical);
}
private void UpdateLayoutForOrientation(bool isVertical)
{
if (isVertical)
{
Grid.SetColumn(left, 1);
Grid.SetRow(left, 0);
Grid.SetColumn(right, 1);
Grid.SetRow(right, 2);
left.Padding = new Thickness(20, 10);
right.Padding = new Thickness(20, 10);
leftArrow.RenderTransform = new Avalonia.Media.RotateTransform(90);
rightArrow.RenderTransform = new Avalonia.Media.RotateTransform(90);
}
else
{
Grid.SetColumn(left, 0);
Grid.SetRow(left, 1);
Grid.SetColumn(right, 2);
Grid.SetRow(right, 1);
left.Padding = new Thickness(10, 20);
right.Padding = new Thickness(10, 20);
leftArrow.RenderTransform = null;
rightArrow.RenderTransform = null;
}
}
}

98
samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml

@ -0,0 +1,98 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CareCompanionAppPage">
<UserControl.Styles>
<Style Selector="PipsPager /template/ ListBox ListBoxItem">
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="2,0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<ControlTemplate>
<Grid Background="Transparent">
<Border Name="Pip"
Width="6" Height="6" CornerRadius="3"
HorizontalAlignment="Center" VerticalAlignment="Center"
Background="#d1d5db">
<Border.Transitions>
<Transitions>
<DoubleTransition Property="Width" Duration="0:0:0.2" Easing="CubicEaseOut" />
<DoubleTransition Property="Height" Duration="0:0:0.2" Easing="CubicEaseOut" />
<CornerRadiusTransition Property="CornerRadius" Duration="0:0:0.2" Easing="CubicEaseOut" />
<BrushTransition Property="Background" Duration="0:0:0.2" />
</Transitions>
</Border.Transitions>
</Border>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:pointerover /template/ Border#Pip">
<Setter Property="Width" Value="8" />
<Setter Property="Height" Value="8" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Background" Value="#b0b0b0" />
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:selected /template/ Border#Pip">
<Setter Property="Width" Value="20" />
<Setter Property="Height" Value="6" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Background" Value="#137fec" />
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:selected:pointerover /template/ Border#Pip">
<Setter Property="Width" Value="20" />
<Setter Property="Height" Value="6" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Background" Value="#0a5bb5" />
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:pressed /template/ Border#Pip">
<Setter Property="Width" Value="6" />
<Setter Property="Height" Value="6" />
<Setter Property="Background" Value="#909090" />
</Style>
</UserControl.Styles>
<DockPanel>
<ScrollViewer x:Name="InfoPanel" DockPanel.Dock="Right" Width="300">
<StackPanel Margin="16" Spacing="16">
<TextBlock Text="Care Companion" FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.7"
Text="A patient-focused healthcare companion app. CarouselPage handles onboarding (swipe or tap Next) with PipsPager as custom pill-shaped page indicators. Tapping Skip or Get Started pushes the TabbedPage dashboard and removes the onboarding from the navigation stack." />
<Separator />
<TextBlock Text="Navigation Flow" FontSize="13" FontWeight="SemiBold" />
<StackPanel Spacing="4">
<TextBlock FontSize="12" TextWrapping="Wrap" Text="1. CarouselPage is the NavigationPage root" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="2. Swipe or tap Next to advance pages" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="3. Skip jumps to Get Started page" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="4. Get Started pushes TabbedPage dashboard" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="5. RemovePage(carousel) cleans the stack" />
</StackPanel>
<Separator />
<TextBlock Text="Design" FontSize="13" FontWeight="SemiBold" />
<StackPanel Spacing="4">
<TextBlock FontSize="12" Text="Primary: #137fec (healthcare blue)" />
<TextBlock FontSize="12" Text="Background: #f6f7f8 (off-white)" />
<TextBlock FontSize="12" Text="Cards: #ffffff with border" />
<TextBlock FontSize="12" Text="Success: #10b981 (streak/done)" />
<TextBlock FontSize="12" Text="Roundness: 12-16px corners" />
</StackPanel>
</StackPanel>
</ScrollViewer>
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="8" ClipToBounds="True">
<NavigationPage x:Name="NavPage" />
</Border>
</DockPanel>
</UserControl>

1068
samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml.cs

File diff suppressed because it is too large

119
samples/ControlCatalog/Pages/CarouselPage/CarouselCustomizationPage.xaml

@ -0,0 +1,119 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselCustomizationPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="260">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" />
<Button x:Name="PreviousButton"
Content="Previous"
HorizontalAlignment="Stretch" />
<Button x:Name="NextButton"
Content="Next"
HorizontalAlignment="Stretch" />
<Separator />
<TextBlock Text="Orientation" FontWeight="SemiBold" FontSize="13" />
<ComboBox x:Name="OrientationCombo"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<ComboBoxItem>Horizontal</ComboBoxItem>
<ComboBoxItem>Vertical</ComboBoxItem>
</ComboBox>
<TextBlock Text="Viewport Fraction" FontWeight="SemiBold" FontSize="13" />
<Grid ColumnDefinitions="*,48" ColumnSpacing="8">
<Slider x:Name="ViewportSlider"
Minimum="0.33"
Maximum="1.0"
Value="1.0"
TickFrequency="0.01"
HorizontalAlignment="Stretch" />
<TextBlock x:Name="ViewportLabel"
Grid.Column="1"
Text="1.00"
VerticalAlignment="Center"
HorizontalAlignment="Right"
FontWeight="SemiBold" />
</Grid>
<TextBlock x:Name="ViewportHint"
Text="1.00 shows a single full page."
FontSize="11"
Opacity="0.6"
TextWrapping="Wrap" />
<Separator />
<TextBlock Text="Options" FontWeight="SemiBold" FontSize="14" />
<CheckBox x:Name="WrapSelectionCheck"
Content="Wrap Selection"
IsChecked="False"
IsCheckedChanged="OnWrapSelectionChanged" />
<CheckBox x:Name="SwipeEnabledCheck"
Content="Swipe Enabled"
IsChecked="False"
IsCheckedChanged="OnSwipeEnabledChanged" />
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" />
<TextBlock x:Name="StatusText"
Text="Orientation: Horizontal"
Opacity="0.7"
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<Carousel x:Name="DemoCarousel" Height="300">
<Carousel.PageTransition>
<PageSlide Duration="0.25" Orientation="Horizontal" />
</Carousel.PageTransition>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/delicate-arch-896885_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 1: Delicate Arch" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/hirsch-899118_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 2: Hirsch" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 3: Maple Leaf" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
</Carousel>
</Border>
</DockPanel>
</UserControl>

48
samples/ControlCatalog/Pages/CarouselPage/CarouselCustomizationPage.xaml.cs

@ -0,0 +1,48 @@
using System;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselCustomizationPage : UserControl
{
public CarouselCustomizationPage()
{
InitializeComponent();
PreviousButton.Click += (_, _) => DemoCarousel.Previous();
NextButton.Click += (_, _) => DemoCarousel.Next();
OrientationCombo.SelectionChanged += (_, _) => ApplyOrientation();
ViewportSlider.ValueChanged += OnViewportFractionChanged;
}
private void ApplyOrientation()
{
var horizontal = OrientationCombo.SelectedIndex == 0;
var axis = horizontal ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical;
DemoCarousel.PageTransition = new PageSlide(TimeSpan.FromSeconds(0.25), axis);
StatusText.Text = $"Orientation: {(horizontal ? "Horizontal" : "Vertical")}";
}
private void OnViewportFractionChanged(object? sender, RangeBaseValueChangedEventArgs e)
{
var value = Math.Round(e.NewValue, 2);
DemoCarousel.ViewportFraction = value;
ViewportLabel.Text = value.ToString("0.00");
ViewportHint.Text = value >= 1d ?
"1.00 shows a single full page." :
$"{1d / value:0.##} pages fit in view. Try 0.80 for peeking.";
}
private void OnWrapSelectionChanged(object? sender, RoutedEventArgs e)
{
DemoCarousel.WrapSelection = WrapSelectionCheck.IsChecked == true;
}
private void OnSwipeEnabledChanged(object? sender, RoutedEventArgs e)
{
DemoCarousel.IsSwipeEnabled = SwipeEnabledCheck.IsChecked == true;
}
}
}

60
samples/ControlCatalog/Pages/CarouselPage/CarouselDataBindingPage.xaml

@ -0,0 +1,60 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:ControlCatalog.Pages"
x:Class="ControlCatalog.Pages.CarouselDataBindingPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="260">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Collection" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" />
<Button x:Name="PreviousButton" Content="Previous" HorizontalAlignment="Stretch" />
<Button x:Name="NextButton" Content="Next" HorizontalAlignment="Stretch" />
<Separator />
<TextBlock Text="Modify Items" FontWeight="SemiBold" FontSize="13" />
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.6"
Text="The Carousel is bound to an ObservableCollection. Changes reflect immediately." />
<Button x:Name="AddButton" Content="Add Item" HorizontalAlignment="Stretch" />
<Button x:Name="RemoveButton" Content="Remove Current" HorizontalAlignment="Stretch" />
<Button x:Name="ShuffleButton" Content="Shuffle" HorizontalAlignment="Stretch" />
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" />
<TextBlock x:Name="StatusText" Text="Item: 1 / 4"
Opacity="0.7" TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="6" ClipToBounds="True">
<Carousel x:Name="DemoCarousel"
Height="280"
IsSwipeEnabled="True">
<Carousel.PageTransition>
<CrossFade Duration="0.3" />
</Carousel.PageTransition>
<Carousel.ItemTemplate>
<DataTemplate x:DataType="pages:CarouselCardItem">
<Border CornerRadius="14" Margin="14,12" ClipToBounds="True"
Background="{Binding Background}">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8">
<TextBlock Text="{Binding Number}" FontSize="52" FontWeight="Bold"
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="{Binding Title}" FontSize="15" FontWeight="SemiBold"
Foreground="{Binding Accent}" HorizontalAlignment="Center" />
</StackPanel>
</Border>
</DataTemplate>
</Carousel.ItemTemplate>
</Carousel>
</Border>
</DockPanel>
</UserControl>

95
samples/ControlCatalog/Pages/CarouselPage/CarouselDataBindingPage.xaml.cs

@ -0,0 +1,95 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
namespace ControlCatalog.Pages
{
public class CarouselCardItem
{
public string Number { get; set; } = "";
public string Title { get; set; } = "";
public IBrush Background { get; set; } = Brushes.Gray;
public IBrush Accent { get; set; } = Brushes.White;
}
public partial class CarouselDataBindingPage : UserControl
{
private static readonly (string Title, string Color, string Accent)[] Palette =
{
("Neon Pulse", "#3525CD", "#C3C0FF"), ("Ephemeral Blue", "#0891B2", "#BAF0FA"),
("Forest Forms", "#059669", "#A7F3D0"), ("Golden Hour", "#D97706", "#FDE68A"),
("Crimson Wave", "#BE185D", "#FBCFE8"), ("Stone Age", "#57534E", "#D6D3D1"),
};
private readonly ObservableCollection<CarouselCardItem> _items = new();
private int _addCounter;
public CarouselDataBindingPage()
{
InitializeComponent();
DemoCarousel.ItemsSource = _items;
DemoCarousel.SelectionChanged += OnSelectionChanged;
for (var i = 0; i < 4; i++)
AppendItem();
PreviousButton.Click += (_, _) => DemoCarousel.Previous();
NextButton.Click += (_, _) => DemoCarousel.Next();
AddButton.Click += OnAddItem;
RemoveButton.Click += OnRemoveCurrent;
ShuffleButton.Click += OnShuffle;
UpdateStatus();
}
private void AppendItem()
{
var (title, color, accent) = Palette[_addCounter % Palette.Length];
_items.Add(new CarouselCardItem
{
Number = $"{_items.Count + 1:D2}",
Title = title,
Background = new SolidColorBrush(Color.Parse(color)),
Accent = new SolidColorBrush(Color.Parse(accent)),
});
_addCounter++;
}
private void OnAddItem(object? sender, RoutedEventArgs e)
{
AppendItem();
UpdateStatus();
}
private void OnRemoveCurrent(object? sender, RoutedEventArgs e)
{
if (_items.Count == 0)
return;
var idx = Math.Clamp(DemoCarousel.SelectedIndex, 0, _items.Count - 1);
_items.RemoveAt(idx);
UpdateStatus();
}
private void OnShuffle(object? sender, RoutedEventArgs e)
{
var rng = new Random();
var shuffled = _items.OrderBy(_ => rng.Next()).ToList();
_items.Clear();
foreach (var item in shuffled)
_items.Add(item);
UpdateStatus();
}
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
UpdateStatus();
}
private void UpdateStatus()
{
StatusText.Text = $"Item: {DemoCarousel.SelectedIndex + 1} / {_items.Count}";
}
}
}

557
samples/ControlCatalog/Pages/CarouselPage/CarouselGalleryAppPage.xaml

@ -0,0 +1,557 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselGalleryAppPage"
Background="#F8F9FB">
<UserControl.Resources>
<!-- White pip colors for the hero dark background -->
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForeground" Color="#7FFFFFFF" />
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForegroundPointerOver" Color="#BFFFFFFF" />
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForegroundPressed" Color="#BFFFFFFF" />
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForegroundSelected" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="PipsPagerSelectionIndicatorForegroundDisabled" Color="#3FFFFFFF" />
<SolidColorBrush x:Key="PipsPagerNavigationButtonForeground" Color="#7FFFFFFF" />
<SolidColorBrush x:Key="PipsPagerNavigationButtonForegroundPointerOver" Color="#BFFFFFFF" />
<SolidColorBrush x:Key="PipsPagerNavigationButtonForegroundPressed" Color="#BFFFFFFF" />
</UserControl.Resources>
<DockPanel>
<!-- Right info panel — visible when width >= 640px -->
<ScrollViewer x:Name="InfoPanel" DockPanel.Dock="Right" Width="290" IsVisible="False">
<StackPanel Margin="16" Spacing="16">
<TextBlock Text="Curated Gallery" FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.7"
Text="Art gallery editorial app showcasing a full-bleed hero Carousel synced with a pill-shaped PipsPager, a peek Collection Highlights scroll list, Curators' Choice cards, and a Join the Circle subscription section. Navigation via DrawerPage side menu." />
<Separator />
<TextBlock Text="Navigation Flow" FontSize="13" FontWeight="SemiBold" />
<StackPanel Spacing="4">
<TextBlock FontSize="12" TextWrapping="Wrap" Text="1. DrawerPage: root, side placement" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="2. Hamburger overlaid on hero opens the drawer pane" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="3. Hero: full-bleed Carousel + PipsPager (pill dots)" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="4. PipsPager synced bidirectionally with Carousel" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="5. Mouse drag on hero navigates carousel slides" />
</StackPanel>
<Separator />
<TextBlock Text="Key Code" FontSize="13" FontWeight="SemiBold" />
<Border Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
CornerRadius="4" Padding="8">
<TextBlock FontFamily="Cascadia Code,Consolas,Menlo,monospace"
FontSize="10" TextWrapping="Wrap"
Text="HeroCarousel.SelectionChanged&#xA; += OnHeroSelectionChanged;&#xA;HeroPager.SelectedIndexChanged&#xA; += OnPagerIndexChanged;&#xA;&#xA;// Bidirectional sync guard&#xA;if (_syncing) return;&#xA;_syncing = true;&#xA;HeroPager.SelectedPageIndex&#xA; = HeroCarousel.SelectedIndex;&#xA;_syncing = false;" />
</Border>
</StackPanel>
</ScrollViewer>
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="8" ClipToBounds="True">
<DrawerPage x:Name="RootDrawer"
DrawerLength="260"
IsGestureEnabled="True">
<DrawerPage.Styles>
<!-- Hide the DrawerPage built-in top bar so only our custom hero overlay bar is shown -->
<Style Selector="DrawerPage#RootDrawer /template/ Border#PART_TopBar">
<Setter Property="IsVisible" Value="False" />
</Style>
</DrawerPage.Styles>
<!-- Drawer header -->
<DrawerPage.DrawerHeader>
<Border Background="#3525CD" Padding="20,32,20,20">
<StackPanel Spacing="4">
<TextBlock Text="CURATED"
FontSize="20"
FontWeight="Bold"
Foreground="White"
LetterSpacing="-0.4" />
<TextBlock Text="The Digital Gallery"
FontSize="12"
Foreground="#C3C0FF"
Opacity="0.85" />
</StackPanel>
</Border>
</DrawerPage.DrawerHeader>
<!-- Drawer menu -->
<DrawerPage.Drawer>
<StackPanel Background="#F8F9FB">
<ListBox x:Name="DrawerMenu"
Background="Transparent"
SelectionChanged="OnDrawerMenuSelectionChanged">
<ListBoxItem Padding="20,14">
<StackPanel Orientation="Horizontal" Spacing="16">
<Path Data="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
Fill="#3525CD" Width="20" Height="20" Stretch="Uniform" />
<TextBlock Text="Discover" FontSize="15" FontWeight="SemiBold"
Foreground="#191C1E" VerticalAlignment="Center" />
</StackPanel>
</ListBoxItem>
<ListBoxItem Padding="20,14">
<StackPanel Orientation="Horizontal" Spacing="16">
<Path Data="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"
Fill="#464555" Width="20" Height="20" Stretch="Uniform" />
<TextBlock Text="Collection" FontSize="15"
Foreground="#464555" VerticalAlignment="Center" />
</StackPanel>
</ListBoxItem>
<ListBoxItem Padding="20,14">
<StackPanel Orientation="Horizontal" Spacing="16">
<Path Data="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14l-5-5 1.41-1.41L12 14.17l7.59-7.59L21 8l-9 9z"
Fill="#464555" Width="20" Height="20" Stretch="Uniform" />
<TextBlock Text="Archive" FontSize="15"
Foreground="#464555" VerticalAlignment="Center" />
</StackPanel>
</ListBoxItem>
<ListBoxItem Padding="20,14">
<StackPanel Orientation="Horizontal" Spacing="16">
<Path Data="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
Fill="#464555" Width="20" Height="20" Stretch="Uniform" />
<TextBlock Text="Profile" FontSize="15"
Foreground="#464555" VerticalAlignment="Center" />
</StackPanel>
</ListBoxItem>
</ListBox>
<Separator Margin="20,8" />
<StackPanel Margin="20,8" Spacing="0">
<TextBlock Text="EXHIBITIONS"
FontSize="11"
FontWeight="Bold"
Foreground="#777587"
LetterSpacing="1.2"
Margin="0,0,0,16" />
<Grid ColumnDefinitions="4,*" Margin="0,0,0,14">
<Border Grid.Column="0" Width="4" Height="38"
CornerRadius="2" Background="#3525CD"
VerticalAlignment="Center" Margin="0,0,14,0" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Text="Neon Pulse" FontSize="14" FontWeight="SemiBold"
Foreground="#191C1E" />
<TextBlock Text="Opens March 20" FontSize="11" Foreground="#777587" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="4,*" Margin="0,0,0,14">
<Border Grid.Column="0" Width="4" Height="38"
CornerRadius="2" Background="#4F46E5"
VerticalAlignment="Center" Margin="0,0,14,0" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Text="Fragmented Forms" FontSize="14" FontWeight="SemiBold"
Foreground="#191C1E" />
<TextBlock Text="Now Open" FontSize="11" Foreground="#4F46E5" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="4,*">
<Border Grid.Column="0" Width="4" Height="38"
CornerRadius="2" Background="#B84B00"
VerticalAlignment="Center" Margin="0,0,14,0" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Text="The Digital Horizon" FontSize="14" FontWeight="SemiBold"
Foreground="#191C1E" />
<TextBlock Text="Closing Soon" FontSize="11" Foreground="#B84B00" />
</StackPanel>
</Grid>
</StackPanel>
</StackPanel>
</DrawerPage.Drawer>
<!-- Main content: hero carousel IS the header -->
<DrawerPage.Content>
<Grid RowDefinitions="Auto,*">
<!-- Row 0: Hero carousel header — also handles mouse drag for swipe navigation -->
<Grid Height="320"
PointerPressed="OnHeroPointerPressed"
PointerReleased="OnHeroPointerReleased"
PointerCaptureLost="OnHeroPointerCaptureLost">
<!-- Full-bleed hero carousel -->
<Carousel x:Name="HeroCarousel"
IsSwipeEnabled="True">
<Carousel.PageTransition>
<PageSlide Duration="0.35" Orientation="Horizontal" />
</Carousel.PageTransition>
<!-- Hero 1 -->
<Grid>
<Image Source="/Assets/ModernApp/gallery_city.jpg" Stretch="UniformToFill" />
<Border>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#88000000" Offset="0" />
<GradientStop Color="#00000000" Offset="0.35" />
<GradientStop Color="#00000000" Offset="0.55" />
<GradientStop Color="#CC000000" Offset="1" />
</LinearGradientBrush>
</Border.Background>
</Border>
<StackPanel VerticalAlignment="Bottom" Margin="20,0,20,44" Spacing="4">
<TextBlock Text="FEATURED EXHIBITION"
FontSize="11" FontWeight="Bold"
Foreground="#C3C0FF" LetterSpacing="1.5" />
<TextBlock Text="Neon Pulse: The New Abstract"
FontSize="22" FontWeight="Bold"
Foreground="White" TextWrapping="Wrap" LetterSpacing="-0.4" />
</StackPanel>
</Grid>
<!-- Hero 2 -->
<Grid>
<Image Source="/Assets/ModernApp/gallery_alpine.jpg" Stretch="UniformToFill" />
<Border>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#88000000" Offset="0" />
<GradientStop Color="#00000000" Offset="0.35" />
<GradientStop Color="#00000000" Offset="0.55" />
<GradientStop Color="#CC000000" Offset="1" />
</LinearGradientBrush>
</Border.Background>
</Border>
<StackPanel VerticalAlignment="Bottom" Margin="20,0,20,44" Spacing="4">
<TextBlock Text="NOW OPEN"
FontSize="11" FontWeight="Bold"
Foreground="#C3C0FF" LetterSpacing="1.5" />
<TextBlock Text="Fragmented Forms: Sculpture Today"
FontSize="22" FontWeight="Bold"
Foreground="White" TextWrapping="Wrap" LetterSpacing="-0.4" />
</StackPanel>
</Grid>
<!-- Hero 3 -->
<Grid>
<Image Source="/Assets/ModernApp/gallery_venice.jpg" Stretch="UniformToFill" />
<Border>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#88000000" Offset="0" />
<GradientStop Color="#00000000" Offset="0.35" />
<GradientStop Color="#00000000" Offset="0.55" />
<GradientStop Color="#CC000000" Offset="1" />
</LinearGradientBrush>
</Border.Background>
</Border>
<StackPanel VerticalAlignment="Bottom" Margin="20,0,20,44" Spacing="4">
<TextBlock Text="CLOSING SOON"
FontSize="11" FontWeight="Bold"
Foreground="#FFCDD2" LetterSpacing="1.5" />
<TextBlock Text="The Digital Horizon: Web3 &amp; Generative Art"
FontSize="22" FontWeight="Bold"
Foreground="White" TextWrapping="Wrap" LetterSpacing="-0.4" />
</StackPanel>
</Grid>
</Carousel>
<!-- PipsPager overlaid near bottom of hero — pill-shaped, no nav arrows -->
<PipsPager x:Name="HeroPager"
NumberOfPages="3"
IsPreviousButtonVisible="False"
IsNextButtonVisible="False"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,18">
<PipsPager.Styles>
<Style Selector="PipsPager /template/ ListBox ListBoxItem">
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="2,0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<ControlTemplate>
<Grid Background="Transparent">
<Border Name="Pip"
Width="6" Height="6" CornerRadius="3"
HorizontalAlignment="Center" VerticalAlignment="Center"
Background="#7FFFFFFF">
<Border.Transitions>
<Transitions>
<DoubleTransition Property="Width" Duration="0:0:0.2" Easing="CubicEaseOut" />
<CornerRadiusTransition Property="CornerRadius" Duration="0:0:0.2" Easing="CubicEaseOut" />
<BrushTransition Property="Background" Duration="0:0:0.2" />
</Transitions>
</Border.Transitions>
</Border>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:pointerover /template/ Border#Pip">
<Setter Property="Width" Value="8" />
<Setter Property="Height" Value="8" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Background" Value="#BFFFFFFF" />
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:selected /template/ Border#Pip">
<Setter Property="Width" Value="22" />
<Setter Property="Height" Value="6" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Background" Value="#FFFFFFFF" />
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:selected:pointerover /template/ Border#Pip">
<Setter Property="Width" Value="22" />
<Setter Property="Height" Value="6" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Background" Value="#E8FFFFFF" />
</Style>
</PipsPager.Styles>
</PipsPager>
<!-- Top bar overlaid on hero -->
<Grid ColumnDefinitions="Auto,*,Auto"
VerticalAlignment="Top"
Margin="4,8,4,0">
<Button Grid.Column="0"
Background="Transparent"
BorderThickness="0"
Padding="12,8"
Click="OnHamburgerClick">
<Path Data="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"
Fill="White" Width="22" Height="22" Stretch="Uniform" />
</Button>
<TextBlock Grid.Column="1"
Text="Curated"
FontSize="18"
FontWeight="Bold"
Foreground="White"
VerticalAlignment="Center"
HorizontalAlignment="Center"
LetterSpacing="-0.3" />
<Button Grid.Column="2"
Background="Transparent"
BorderThickness="0"
Padding="12,8">
<Path Data="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
Fill="White" Width="22" Height="22" Stretch="Uniform" />
</Button>
</Grid>
</Grid>
<!-- Row 1: Scrollable body -->
<ScrollViewer Grid.Row="1"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<StackPanel>
<!-- Collection Highlights -->
<StackPanel Margin="0,28,0,0">
<Grid ColumnDefinitions="*,Auto" Margin="20,0,20,16">
<TextBlock Text="Collection Highlights"
FontSize="18" FontWeight="Bold"
Foreground="#191C1E" LetterSpacing="-0.3" />
<TextBlock Grid.Column="1"
Text="SEE ALL"
FontSize="12" FontWeight="Bold"
Foreground="#3525CD" LetterSpacing="0.8"
VerticalAlignment="Center" />
</Grid>
<ScrollViewer HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Disabled"
Margin="20,0,0,0">
<StackPanel Orientation="Horizontal" Spacing="10">
<Border Width="180" Height="210" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/ModernApp/gallery_paris.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12,10">
<StackPanel Spacing="2">
<TextBlock Text="SCULPTURE" FontSize="10" FontWeight="Bold"
Foreground="#C3C0FF" LetterSpacing="1" />
<TextBlock Text="Fragmented Grace" FontSize="13"
FontWeight="SemiBold" Foreground="White" />
</StackPanel>
</Border>
</Grid>
</Border>
<Border Width="180" Height="210" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/ModernApp/gallery_bay.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12,10">
<StackPanel Spacing="2">
<TextBlock Text="OIL PAINTING" FontSize="10" FontWeight="Bold"
Foreground="#C3C0FF" LetterSpacing="1" />
<TextBlock Text="Ephemeral Blue" FontSize="13"
FontWeight="SemiBold" Foreground="White" />
</StackPanel>
</Border>
</Grid>
</Border>
<Border Width="180" Height="210" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/ModernApp/gallery_tropical.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12,10">
<StackPanel Spacing="2">
<TextBlock Text="TEXTILE" FontSize="10" FontWeight="Bold"
Foreground="#C3C0FF" LetterSpacing="1" />
<TextBlock Text="Interwoven Lines" FontSize="13"
FontWeight="SemiBold" Foreground="White" />
</StackPanel>
</Border>
</Grid>
</Border>
<Border Width="180" Height="210" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/ModernApp/gallery_alpine.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12,10">
<StackPanel Spacing="2">
<TextBlock Text="PHOTOGRAPHY" FontSize="10" FontWeight="Bold"
Foreground="#C3C0FF" LetterSpacing="1" />
<TextBlock Text="Silent Mountains" FontSize="13"
FontWeight="SemiBold" Foreground="White" />
</StackPanel>
</Border>
</Grid>
</Border>
<!-- Padding card to reveal peek of last item -->
<Border Width="20" Height="210" />
</StackPanel>
</ScrollViewer>
</StackPanel>
<!-- Curators' Choice -->
<StackPanel Margin="20,32,20,0" Spacing="12">
<TextBlock Text="Curators' Choice"
FontSize="20" FontWeight="Bold"
Foreground="#191C1E" HorizontalAlignment="Center"
LetterSpacing="-0.3" />
<TextBlock Text="Hand-picked selections from our global network of artists."
FontSize="13" Foreground="#777587"
HorizontalAlignment="Center" TextAlignment="Center"
TextWrapping="Wrap" Margin="0,0,0,8" />
<!-- Two-column layout: large card left, two stacked badge cards right -->
<Grid ColumnDefinitions="*,130">
<!-- Left: main feature card -->
<Border Grid.Column="0" Background="White" CornerRadius="16"
Padding="20" Margin="0,0,10,0"
BoxShadow="0 2 16 0 #12191C1E">
<StackPanel Spacing="10">
<Path Data="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5z"
Fill="#3525CD" Width="22" Height="22" Stretch="Uniform"
HorizontalAlignment="Left" />
<TextBlock Text="The Digital Horizon"
FontSize="17" FontWeight="Bold"
Foreground="#191C1E" TextWrapping="Wrap" />
<TextBlock Text="Exploring Web3 and Generative Art"
FontSize="13" Foreground="#777587" TextWrapping="Wrap" />
<Button Content="EXPLORE"
Margin="0,10,0,0" Padding="20,11"
FontSize="11" FontWeight="Bold" LetterSpacing="0.8"
CornerRadius="22" Foreground="White" HorizontalAlignment="Left">
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#3525CD" Offset="0" />
<GradientStop Color="#4F46E5" Offset="1" />
</LinearGradientBrush>
</Button.Background>
</Button>
</StackPanel>
</Border>
<!-- Right: two stacked badge cards -->
<StackPanel Grid.Column="1" Spacing="10">
<Border Background="White" CornerRadius="16"
BoxShadow="0 2 16 0 #12191C1E"
Padding="12">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"
Spacing="8" Margin="0,12">
<Path Data="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z"
Fill="#B84B00" Width="28" Height="28" Stretch="Uniform"
HorizontalAlignment="Center" />
<TextBlock Text="TRENDING"
FontSize="10" FontWeight="Bold"
Foreground="#B84B00" LetterSpacing="1"
HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Background="#EDEEF0" CornerRadius="16" Padding="12">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"
Spacing="8" Margin="0,12">
<Path Data="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1.41 14.06L6.17 11.64l1.42-1.41 2.99 3 6.01-6.01 1.42 1.41-7.42 7.43z"
Fill="#464555" Width="28" Height="28" Stretch="Uniform"
HorizontalAlignment="Center" />
<TextBlock Text="VERIFIED"
FontSize="10" FontWeight="Bold"
Foreground="#464555" LetterSpacing="1"
HorizontalAlignment="Center" />
</StackPanel>
</Border>
</StackPanel>
</Grid>
</StackPanel>
<!-- Join the Circle -->
<Border Margin="20,32,20,32" CornerRadius="20" Padding="24" ClipToBounds="True">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#3525CD" Offset="0" />
<GradientStop Color="#4F46E5" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel Spacing="10">
<TextBlock Text="Join the Circle"
FontSize="20" FontWeight="Bold"
Foreground="White" LetterSpacing="-0.3" />
<TextBlock Text="Receive exclusive invitations to private viewings and new drop alerts."
FontSize="13" Foreground="#C3C0FF"
TextWrapping="Wrap" Opacity="0.9" LineHeight="20" />
<TextBox PlaceholderText="Your email address"
Margin="0,6,0,0"
CornerRadius="12"
BorderThickness="1"
Padding="14,12"
Foreground="White"
PlaceholderForeground="#9896D8">
<TextBox.Resources>
<SolidColorBrush x:Key="TextControlBackground" Color="#3C38B5" />
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#4440BE" />
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#3C38B5" />
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#5552C8" />
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#7370D8" />
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#8B88E8" />
</TextBox.Resources>
</TextBox>
<Button Content="SUBSCRIBE"
Margin="0,2,0,0" Padding="24,12"
FontSize="12" FontWeight="Bold" LetterSpacing="1"
CornerRadius="24" Foreground="#3525CD" Background="White"
HorizontalAlignment="Left" />
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
</DrawerPage.Content>
</DrawerPage>
</Border>
</DockPanel>
</UserControl>

101
samples/ControlCatalog/Pages/CarouselPage/CarouselGalleryAppPage.xaml.cs

@ -0,0 +1,101 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselGalleryAppPage : UserControl
{
private bool _syncing;
private Point _dragStart;
private bool _isDragging;
private const double SwipeThreshold = 50;
private ScrollViewer? _infoPanel;
public CarouselGalleryAppPage()
{
InitializeComponent();
_infoPanel = this.FindControl<ScrollViewer>("InfoPanel");
HeroCarousel.SelectionChanged += OnHeroSelectionChanged;
HeroPager.SelectedIndexChanged += OnPagerIndexChanged;
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
UpdateInfoPanelVisibility();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == BoundsProperty)
UpdateInfoPanelVisibility();
}
private void UpdateInfoPanelVisibility()
{
if (_infoPanel != null)
_infoPanel.IsVisible = Bounds.Width >= 640;
}
private void OnHamburgerClick(object? sender, RoutedEventArgs e)
{
RootDrawer.IsOpen = !RootDrawer.IsOpen;
}
private void OnHeroSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_syncing)
return;
_syncing = true;
HeroPager.SelectedPageIndex = HeroCarousel.SelectedIndex;
_syncing = false;
}
private void OnPagerIndexChanged(object? sender, PipsPagerSelectedIndexChangedEventArgs e)
{
if (_syncing)
return;
_syncing = true;
HeroCarousel.SelectedIndex = e.NewIndex;
_syncing = false;
}
private void OnDrawerMenuSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
RootDrawer.IsOpen = false;
DrawerMenu.SelectedItem = null;
}
private void OnHeroPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(null).Properties.IsLeftButtonPressed)
return;
_dragStart = e.GetPosition((Visual?)sender);
_isDragging = true;
}
private void OnHeroPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (!_isDragging)
return;
_isDragging = false;
var delta = e.GetPosition((Visual?)sender).X - _dragStart.X;
if (Math.Abs(delta) < SwipeThreshold)
return;
if (delta < 0)
HeroCarousel.Next();
else
HeroCarousel.Previous();
}
private void OnHeroPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
{
_isDragging = false;
}
}
}

93
samples/ControlCatalog/Pages/CarouselPage/CarouselGesturesPage.xaml

@ -0,0 +1,93 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselGesturesPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="260">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Options" FontWeight="SemiBold" FontSize="13" />
<CheckBox x:Name="SwipeCheck"
Content="Swipe Gesture"
IsChecked="True"
IsCheckedChanged="OnSwipeEnabledChanged" />
<CheckBox x:Name="WrapCheck"
Content="Wrap Selection"
IsChecked="False"
IsCheckedChanged="OnWrapSelectionChanged" />
<CheckBox x:Name="KeyboardCheck"
Content="Keyboard Navigation"
IsChecked="True"
IsCheckedChanged="OnKeyboardEnabledChanged" />
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" />
<TextBlock x:Name="StatusText"
Text="Item: 1 / 3"
Opacity="0.7"
TextWrapping="Wrap" />
<TextBlock x:Name="LastActionText"
Text="Action: —"
Opacity="0.7"
TextWrapping="Wrap" />
<TextBlock Text="Swipe left/right or use arrow keys to navigate."
FontSize="11"
Opacity="0.5"
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<Carousel x:Name="DemoCarousel"
Focusable="True"
IsSwipeEnabled="True"
Height="300">
<Carousel.PageTransition>
<PageSlide Duration="0.25" Orientation="Horizontal" />
</Carousel.PageTransition>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/delicate-arch-896885_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 1: Delicate Arch" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/hirsch-899118_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 2: Hirsch" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 3: Maple Leaf" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
</Carousel>
</Border>
</DockPanel>
</UserControl>

59
samples/ControlCatalog/Pages/CarouselPage/CarouselGesturesPage.xaml.cs

@ -0,0 +1,59 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselGesturesPage : UserControl
{
private bool _keyboardEnabled = true;
public CarouselGesturesPage()
{
InitializeComponent();
DemoCarousel.AddHandler(InputElement.KeyDownEvent, OnKeyDown, handledEventsToo: true);
DemoCarousel.SelectionChanged += OnSelectionChanged;
DemoCarousel.Loaded += (_, _) => DemoCarousel.Focus();
}
private void OnKeyDown(object? sender, KeyEventArgs e)
{
if (!_keyboardEnabled)
return;
switch (e.Key)
{
case Key.Left:
case Key.Up:
LastActionText.Text = $"Action: Key {e.Key} (Previous)";
break;
case Key.Right:
case Key.Down:
LastActionText.Text = $"Action: Key {e.Key} (Next)";
break;
}
}
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
StatusText.Text = $"Item: {DemoCarousel.SelectedIndex + 1} / {DemoCarousel.ItemCount}";
if (DemoCarousel.IsSwiping)
LastActionText.Text = "Action: Swipe";
}
private void OnSwipeEnabledChanged(object? sender, RoutedEventArgs e)
{
DemoCarousel.IsSwipeEnabled = SwipeCheck.IsChecked == true;
}
private void OnWrapSelectionChanged(object? sender, RoutedEventArgs e)
{
DemoCarousel.WrapSelection = WrapCheck.IsChecked == true;
}
private void OnKeyboardEnabledChanged(object? sender, RoutedEventArgs e)
{
_keyboardEnabled = KeyboardCheck.IsChecked == true;
}
}
}

74
samples/ControlCatalog/Pages/CarouselPage/CarouselGettingStartedPage.xaml

@ -0,0 +1,74 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselGettingStartedPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="260">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" />
<Button x:Name="PreviousButton"
Content="Previous"
HorizontalAlignment="Stretch" />
<Button x:Name="NextButton"
Content="Next"
HorizontalAlignment="Stretch" />
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" />
<TextBlock x:Name="StatusText"
Text="Item: 1 / 3"
Opacity="0.7"
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<Carousel x:Name="DemoCarousel" Height="300" IsSwipeEnabled="True">
<Carousel.PageTransition>
<PageSlide Duration="0.25" Orientation="Horizontal" />
</Carousel.PageTransition>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/delicate-arch-896885_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 1: Delicate Arch" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/hirsch-899118_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 2: Hirsch" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 3: Maple Leaf" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
</Carousel>
</Border>
</DockPanel>
</UserControl>

40
samples/ControlCatalog/Pages/CarouselPage/CarouselGettingStartedPage.xaml.cs

@ -0,0 +1,40 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselGettingStartedPage : UserControl
{
public CarouselGettingStartedPage()
{
InitializeComponent();
PreviousButton.Click += OnPrevious;
NextButton.Click += OnNext;
DemoCarousel.SelectionChanged += OnSelectionChanged;
}
private void OnPrevious(object? sender, RoutedEventArgs e)
{
DemoCarousel.Previous();
UpdateStatus();
}
private void OnNext(object? sender, RoutedEventArgs e)
{
DemoCarousel.Next();
UpdateStatus();
}
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
UpdateStatus();
}
private void UpdateStatus()
{
var index = DemoCarousel.SelectedIndex + 1;
var count = DemoCarousel.ItemCount;
StatusText.Text = $"Item: {index} / {count}";
}
}
}

140
samples/ControlCatalog/Pages/CarouselPage/CarouselMultiItemPage.xaml

@ -0,0 +1,140 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselMultiItemPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="260">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" />
<Button x:Name="PreviousButton" Content="Previous" HorizontalAlignment="Stretch" />
<Button x:Name="NextButton" Content="Next" HorizontalAlignment="Stretch" />
<Separator />
<TextBlock Text="Viewport Fraction" FontWeight="SemiBold" FontSize="13" />
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.6"
Text="Values below 1.0 show adjacent items peeking into the viewport." />
<Grid ColumnDefinitions="*,48" ColumnSpacing="8">
<Slider x:Name="ViewportSlider"
Minimum="0.2" Maximum="1.0" Value="0.5"
TickFrequency="0.01" IsSnapToTickEnabled="True"
HorizontalAlignment="Stretch"
ValueChanged="OnViewportFractionChanged" />
<TextBlock x:Name="ViewportLabel" Grid.Column="1"
Text="0.50" VerticalAlignment="Center"
HorizontalAlignment="Right" FontWeight="SemiBold" />
</Grid>
<TextBlock x:Name="ViewportHint"
Text="~2 items visible."
FontSize="11" Opacity="0.6" TextWrapping="Wrap" />
<Separator />
<TextBlock Text="Options" FontWeight="SemiBold" FontSize="13" />
<CheckBox x:Name="WrapCheck" Content="Wrap Selection" IsChecked="False"
IsCheckedChanged="OnWrapChanged" />
<CheckBox x:Name="SwipeCheck" Content="Swipe / Drag" IsChecked="True"
IsCheckedChanged="OnSwipeChanged" />
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" />
<TextBlock x:Name="StatusText" Text="Item: 1 / 5"
Opacity="0.7" TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="6" ClipToBounds="True">
<Carousel x:Name="DemoCarousel"
Height="280"
ViewportFraction="0.5"
IsSwipeEnabled="True">
<Carousel.PageTransition>
<PageSlide Duration="0.3" Orientation="Horizontal" />
</Carousel.PageTransition>
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#3525CD" Offset="0" />
<GradientStop Color="#6B5CE7" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8">
<TextBlock Text="01" FontSize="52" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="Neon Pulse" FontSize="15" FontWeight="SemiBold"
Foreground="#C3C0FF" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#0891B2" Offset="0" />
<GradientStop Color="#06B6D4" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8">
<TextBlock Text="02" FontSize="52" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="Ephemeral Blue" FontSize="15" FontWeight="SemiBold"
Foreground="#BAF0FA" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#059669" Offset="0" />
<GradientStop Color="#10B981" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8">
<TextBlock Text="03" FontSize="52" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="Forest Forms" FontSize="15" FontWeight="SemiBold"
Foreground="#A7F3D0" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#D97706" Offset="0" />
<GradientStop Color="#F59E0B" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8">
<TextBlock Text="04" FontSize="52" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="Golden Hour" FontSize="15" FontWeight="SemiBold"
Foreground="#FDE68A" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Margin="6,12" CornerRadius="14" ClipToBounds="True">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#BE185D" Offset="0" />
<GradientStop Color="#EC4899" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8">
<TextBlock Text="05" FontSize="52" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="Crimson Wave" FontSize="15" FontWeight="SemiBold"
Foreground="#FBCFE8" HorizontalAlignment="Center" />
</StackPanel>
</Border>
</Carousel>
</Border>
</DockPanel>
</UserControl>

47
samples/ControlCatalog/Pages/CarouselPage/CarouselMultiItemPage.xaml.cs

@ -0,0 +1,47 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselMultiItemPage : UserControl
{
public CarouselMultiItemPage()
{
InitializeComponent();
PreviousButton.Click += (_, _) => DemoCarousel.Previous();
NextButton.Click += (_, _) => DemoCarousel.Next();
DemoCarousel.SelectionChanged += OnSelectionChanged;
}
private void OnViewportFractionChanged(object? sender, RangeBaseValueChangedEventArgs e)
{
if (DemoCarousel is null)
return;
var value = Math.Round(e.NewValue, 2);
DemoCarousel.ViewportFraction = value;
ViewportLabel.Text = value.ToString("0.00");
ViewportHint.Text = value >= 1d ? "1.00 — single full item." : $"~{1d / value:0.#} items visible.";
}
private void OnWrapChanged(object? sender, RoutedEventArgs e)
{
if (DemoCarousel is null)
return;
DemoCarousel.WrapSelection = WrapCheck.IsChecked == true;
}
private void OnSwipeChanged(object? sender, RoutedEventArgs e)
{
if (DemoCarousel is null)
return;
DemoCarousel.IsSwipeEnabled = SwipeCheck.IsChecked == true;
}
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
StatusText.Text = $"Item: {DemoCarousel.SelectedIndex + 1} / {DemoCarousel.ItemCount}";
}
}
}

115
samples/ControlCatalog/Pages/CarouselPage/CarouselPageCustomizationPage.xaml

@ -0,0 +1,115 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPageCustomizationPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="220">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Slide Direction" FontWeight="SemiBold" FontSize="13" />
<ComboBox x:Name="OrientationCombo" SelectedIndex="0"
SelectionChanged="OnOrientationChanged"
HorizontalAlignment="Stretch">
<ComboBoxItem Content="Horizontal" />
<ComboBoxItem Content="Vertical" />
</ComboBox>
<Separator />
<TextBlock Text="Navigate" FontWeight="SemiBold" FontSize="13" />
<StackPanel Spacing="6">
<Button Content="Previous" Click="OnPrevious" HorizontalAlignment="Stretch" />
<Button Content="Next" Click="OnNext" HorizontalAlignment="Stretch" />
</StackPanel>
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="13" />
<TextBlock x:Name="StatusText" Text="—" Opacity="0.7" TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<Panel>
<CarouselPage x:Name="DemoCarousel">
<ContentPage Header="Sunrise"
Background="#FFFDE68A"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="8">
<Border Width="64" Height="64" CornerRadius="32"
Background="#F59E0B" />
<TextBlock Text="Sunrise" FontSize="28" FontWeight="Bold"
Foreground="#92400E" HorizontalAlignment="Center" />
<TextBlock Text="A new day begins. Warm golden hues fill the horizon."
FontSize="13" Foreground="#92400E" Opacity="0.8"
TextWrapping="Wrap" TextAlignment="Center" MaxWidth="240" />
</StackPanel>
</ContentPage>
<ContentPage Header="Ocean"
Background="#FFBFDBFE"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="8">
<Border Width="64" Height="64" CornerRadius="32"
Background="#3B82F6" />
<TextBlock Text="Ocean" FontSize="28" FontWeight="Bold"
Foreground="#1E3A5F" HorizontalAlignment="Center" />
<TextBlock Text="Vast blue waters stretch beyond what the eye can see."
FontSize="13" Foreground="#1E3A5F" Opacity="0.8"
TextWrapping="Wrap" TextAlignment="Center" MaxWidth="240" />
</StackPanel>
</ContentPage>
<ContentPage Header="Forest"
Background="#FFBBF7D0"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="8">
<Border Width="64" Height="64" CornerRadius="32"
Background="#22C55E" />
<TextBlock Text="Forest" FontSize="28" FontWeight="Bold"
Foreground="#14532D" HorizontalAlignment="Center" />
<TextBlock Text="Ancient trees whisper in the quiet woodland breeze."
FontSize="13" Foreground="#14532D" Opacity="0.8"
TextWrapping="Wrap" TextAlignment="Center" MaxWidth="240" />
</StackPanel>
</ContentPage>
<ContentPage Header="Night"
Background="#1E1B4B"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="8">
<Border Width="64" Height="64" CornerRadius="32"
Background="#6366F1" />
<TextBlock Text="Night" FontSize="28" FontWeight="Bold"
Foreground="#C7D2FE" HorizontalAlignment="Center" />
<TextBlock Text="Stars emerge as the world quiets into peaceful darkness."
FontSize="13" Foreground="#C7D2FE" Opacity="0.8"
TextWrapping="Wrap" TextAlignment="Center" MaxWidth="240" />
</StackPanel>
</ContentPage>
</CarouselPage>
<!-- Page indicator dots -->
<Border VerticalAlignment="Bottom" HorizontalAlignment="Center"
Margin="0,0,0,12" CornerRadius="8" Background="#44000000"
Padding="10,4">
<StackPanel Orientation="Horizontal" Spacing="6">
<Ellipse x:Name="Dot0" Width="8" Height="8" Fill="White" />
<Ellipse x:Name="Dot1" Width="8" Height="8" Fill="White" Opacity="0.4" />
<Ellipse x:Name="Dot2" Width="8" Height="8" Fill="White" Opacity="0.4" />
<Ellipse x:Name="Dot3" Width="8" Height="8" Fill="White" Opacity="0.4" />
</StackPanel>
</Border>
</Panel>
</Border>
</DockPanel>
</UserControl>

79
samples/ControlCatalog/Pages/CarouselPage/CarouselPageCustomizationPage.xaml.cs

@ -0,0 +1,79 @@
using System;
using System.Collections;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselPageCustomizationPage : UserControl
{
public CarouselPageCustomizationPage()
{
InitializeComponent();
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object? sender, RoutedEventArgs e)
{
DemoCarousel.PageTransition = new PageSlide(TimeSpan.FromMilliseconds(300), PageSlide.SlideAxis.Horizontal);
DemoCarousel.SelectionChanged += OnSelectionChanged;
UpdateDots(DemoCarousel.SelectedIndex);
UpdateStatus();
}
private void OnUnloaded(object? sender, RoutedEventArgs e)
{
DemoCarousel.SelectionChanged -= OnSelectionChanged;
}
private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e)
{
UpdateDots(DemoCarousel.SelectedIndex);
UpdateStatus();
}
private void OnOrientationChanged(object? sender, SelectionChangedEventArgs e)
{
if (DemoCarousel == null)
return;
var axis = OrientationCombo.SelectedIndex == 1
? PageSlide.SlideAxis.Vertical
: PageSlide.SlideAxis.Horizontal;
DemoCarousel.PageTransition = new PageSlide(TimeSpan.FromMilliseconds(300), axis);
UpdateStatus();
}
private void OnPrevious(object? sender, RoutedEventArgs e)
{
if (DemoCarousel.SelectedIndex > 0)
DemoCarousel.SelectedIndex--;
}
private void OnNext(object? sender, RoutedEventArgs e)
{
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
if (DemoCarousel.SelectedIndex < pageCount - 1)
DemoCarousel.SelectedIndex++;
}
private void UpdateDots(int selectedIndex)
{
Dot0.Opacity = selectedIndex == 0 ? 1.0 : 0.4;
Dot1.Opacity = selectedIndex == 1 ? 1.0 : 0.4;
Dot2.Opacity = selectedIndex == 2 ? 1.0 : 0.4;
Dot3.Opacity = selectedIndex == 3 ? 1.0 : 0.4;
}
private void UpdateStatus()
{
if (StatusText == null) return;
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
var axis = OrientationCombo?.SelectedIndex == 1 ? "Vertical" : "Horizontal";
StatusText.Text = $"Page {DemoCarousel.SelectedIndex + 1} of {pageCount} | {axis}";
}
}
}

44
samples/ControlCatalog/Pages/CarouselPage/CarouselPageDataTemplatePage.xaml

@ -0,0 +1,44 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPageDataTemplatePage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="240">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Data Templates" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock FontSize="12" Opacity="0.7" TextWrapping="Wrap"
Text="Bind a CarouselPage to a view-model collection, render each item with PageTemplate, and switch templates at runtime." />
<Separator />
<Button Content="Add Page" Click="OnAddPage" HorizontalAlignment="Stretch" />
<Button Content="Remove Last" Click="OnRemovePage" HorizontalAlignment="Stretch" />
<Button Content="Switch Template" Click="OnSwitchTemplate" HorizontalAlignment="Stretch" />
<Separator />
<TextBlock Text="Navigate" FontWeight="SemiBold" FontSize="13" />
<StackPanel Spacing="6">
<Button Content="Previous" Click="OnPrevious" HorizontalAlignment="Stretch" />
<Button Content="Next" Click="OnNext" HorizontalAlignment="Stretch" />
</StackPanel>
<Separator />
<TextBlock x:Name="StatusText" Text="4 pages" Opacity="0.7" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<Panel x:Name="CarouselHost" />
</Border>
</DockPanel>
</UserControl>

209
samples/ControlCatalog/Pages/CarouselPage/CarouselPageDataTemplatePage.xaml.cs

@ -0,0 +1,209 @@
using System.Collections.ObjectModel;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using AvaCarouselPage = Avalonia.Controls.CarouselPage;
namespace ControlCatalog.Pages
{
public partial class CarouselPageDataTemplatePage : UserControl
{
private sealed class CityViewModel
{
public string Name { get; }
public string Color { get; }
public string Description { get; }
public CityViewModel(string name, string color, string description)
{
Name = name;
Color = color;
Description = description;
}
}
private static readonly CityViewModel[] InitialData =
{
new("Tokyo", "#1565C0",
"The neon-lit capital of Japan, where ancient temples meet futuristic skylines."),
new("Amsterdam", "#2E7D32", "A city of canals, bicycles, and world-class museums."),
new("New York", "#6A1B9A", "The city that never sleeps — a cultural and financial powerhouse."),
new("Sydney", "#B71C1C", "Iconic harbour, golden beaches and the world-famous Opera House."),
};
private static readonly CityViewModel[] AddData =
{
new("Paris", "#E65100", "The city of light, love, and the Eiffel Tower."),
new("Barcelona", "#00695C", "Art, architecture, and vibrant street life on the Mediterranean coast."),
new("Kyoto", "#880E4F", "Japan's ancient capital, a living museum of traditional culture."),
};
private readonly ObservableCollection<CityViewModel> _items = new();
private int _addCounter;
private bool _useCardTemplate = true;
private AvaCarouselPage? _carouselPage;
public CarouselPageDataTemplatePage()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_carouselPage != null)
return;
foreach (var vm in InitialData)
_items.Add(vm);
_addCounter = InitialData.Length;
_useCardTemplate = true;
_carouselPage = new AvaCarouselPage { ItemsSource = _items, PageTemplate = CreatePageTemplate() };
_carouselPage.SelectionChanged += OnSelectionChanged;
CarouselHost.Children.Add(_carouselPage);
UpdateStatus();
}
private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e) => UpdateStatus();
private void OnAddPage(object? sender, RoutedEventArgs e)
{
var idx = _addCounter % AddData.Length;
var vm = AddData[idx];
var suffix = _addCounter >= AddData.Length ? $" {_addCounter / AddData.Length + 1}" : "";
_items.Add(new CityViewModel(vm.Name + suffix, vm.Color, vm.Description));
_addCounter++;
UpdateStatus();
}
private void OnRemovePage(object? sender, RoutedEventArgs e)
{
if (_items.Count > 0)
{
_items.RemoveAt(_items.Count - 1);
UpdateStatus();
}
}
private void OnSwitchTemplate(object? sender, RoutedEventArgs e)
{
if (_carouselPage == null)
return;
_useCardTemplate = !_useCardTemplate;
_carouselPage.PageTemplate = CreatePageTemplate();
}
private void OnPrevious(object? sender, RoutedEventArgs e)
{
if (_carouselPage == null)
return;
if (_carouselPage.SelectedIndex > 0)
_carouselPage.SelectedIndex--;
}
private void OnNext(object? sender, RoutedEventArgs e)
{
if (_carouselPage == null)
return;
if (_carouselPage.SelectedIndex < _items.Count - 1)
_carouselPage.SelectedIndex++;
}
private void UpdateStatus()
{
var count = _items.Count;
var index = _carouselPage?.SelectedIndex ?? -1;
StatusText.Text = count == 0 ? "No pages" : $"Page {index + 1} of {count} (index {index})";
}
private IDataTemplate CreatePageTemplate()
{
return new FuncDataTemplate<CityViewModel>((vm, _) => CreatePage(vm, _useCardTemplate));
}
private static ContentPage CreatePage(CityViewModel? vm, bool useCardTemplate)
{
if (vm is null)
return new ContentPage();
return new ContentPage
{
Header = vm.Name, Content = useCardTemplate ? CreateCardContent(vm) : CreateFeatureContent(vm)
};
}
private static Control CreateCardContent(CityViewModel vm)
{
return new StackPanel
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Spacing = 8,
Children =
{
new TextBlock
{
Text = vm.Name,
FontSize = 28,
FontWeight = FontWeight.Bold,
Foreground = new SolidColorBrush(Color.Parse(vm.Color)),
HorizontalAlignment = HorizontalAlignment.Center
},
new TextBlock
{
Text = vm.Description,
FontSize = 13,
Opacity = 0.7,
TextWrapping = TextWrapping.Wrap,
TextAlignment = TextAlignment.Center,
MaxWidth = 280
}
}
};
}
private static Control CreateFeatureContent(CityViewModel vm)
{
var accent = Color.Parse(vm.Color);
return new Border
{
Background = new SolidColorBrush(accent),
Padding = new Thickness(32),
Child = new StackPanel
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Spacing = 12,
Children =
{
new TextBlock
{
Text = vm.Name.ToUpperInvariant(),
FontSize = 34,
FontWeight = FontWeight.Bold,
Foreground = Brushes.White,
HorizontalAlignment = HorizontalAlignment.Center
},
new TextBlock
{
Text = vm.Description,
FontSize = 15,
Foreground = Brushes.White,
Opacity = 0.88,
TextWrapping = TextWrapping.Wrap,
TextAlignment = TextAlignment.Center,
MaxWidth = 320
}
}
}
};
}
}
}

37
samples/ControlCatalog/Pages/CarouselPage/CarouselPageEventsPage.xaml

@ -0,0 +1,37 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPageEventsPage">
<DockPanel>
<StackPanel DockPanel.Dock="Right" Width="240" Margin="0,0,0,0">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Event Log" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Navigate" FontWeight="SemiBold" FontSize="13" />
<StackPanel Spacing="6">
<Button Content="Previous" Click="OnPrevious" HorizontalAlignment="Stretch" />
<Button Content="Next" Click="OnNext" HorizontalAlignment="Stretch" />
</StackPanel>
<Separator />
<Button Content="Clear Log" Click="OnClearLog" HorizontalAlignment="Stretch" />
</StackPanel>
<Border Height="1" Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<ScrollViewer x:Name="LogScrollViewer" VerticalScrollBarVisibility="Auto">
<TextBlock x:Name="EventLog" Margin="12" FontSize="11"
FontFamily="Courier New, Consolas, monospace"
TextWrapping="Wrap" Opacity="0.85" />
</ScrollViewer>
</StackPanel>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="6" ClipToBounds="True">
<CarouselPage x:Name="DemoCarousel" />
</Border>
</DockPanel>
</UserControl>

92
samples/ControlCatalog/Pages/CarouselPage/CarouselPageEventsPage.xaml.cs

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselPageEventsPage : UserControl
{
private readonly List<string> _log = new();
public CarouselPageEventsPage()
{
InitializeComponent();
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object? sender, RoutedEventArgs e)
{
var pageNames = new[] { "Home", "Explore", "Library", "Profile" };
for (int i = 0; i < pageNames.Length; i++)
{
var name = pageNames[i];
var page = new ContentPage
{
Header = name,
Background = NavigationDemoHelper.GetPageBrush(i),
Content = new TextBlock
{
Text = $"{name}",
FontSize = 28,
FontWeight = Avalonia.Media.FontWeight.Bold,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
},
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Stretch,
VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Stretch
};
page.NavigatedTo += (_, args) =>
AppendLog($"NavigatedTo: {name} (from {(args.PreviousPage as ContentPage)?.Header ?? ""})");
page.NavigatedFrom += (_, args) =>
AppendLog($"NavigatedFrom: {name} (to {(args.DestinationPage as ContentPage)?.Header ?? ""})");
((Avalonia.Collections.AvaloniaList<Page>)DemoCarousel.Pages!).Add(page);
}
DemoCarousel.SelectionChanged += OnSelectionChanged;
}
private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e)
{
AppendLog($"SelectionChanged: {(e.PreviousPage as ContentPage)?.Header ?? ""} → {(e.CurrentPage as ContentPage)?.Header ?? ""}");
}
private void OnPrevious(object? sender, RoutedEventArgs e)
{
if (DemoCarousel.SelectedIndex > 0)
DemoCarousel.SelectedIndex--;
}
private void OnNext(object? sender, RoutedEventArgs e)
{
var pageCount = ((AvaloniaList<Page>)DemoCarousel.Pages!).Count;
if (DemoCarousel.SelectedIndex < pageCount - 1)
DemoCarousel.SelectedIndex++;
}
private void OnUnloaded(object? sender, RoutedEventArgs e)
{
DemoCarousel.SelectionChanged -= OnSelectionChanged;
}
private void OnClearLog(object? sender, RoutedEventArgs e)
{
_log.Clear();
EventLog.Text = string.Empty;
}
private void AppendLog(string message)
{
var timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
_log.Add($"[{timestamp}] {message}");
if (_log.Count > 50)
_log.RemoveAt(0);
EventLog.Text = string.Join(Environment.NewLine, _log);
LogScrollViewer.ScrollToEnd();
}
}
}

61
samples/ControlCatalog/Pages/CarouselPage/CarouselPageFirstLookPage.xaml

@ -0,0 +1,61 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPageFirstLookPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="220">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" />
<StackPanel Spacing="6">
<Button Content="Previous" Click="OnPrevious" HorizontalAlignment="Stretch" />
<Button Content="Next" Click="OnNext" HorizontalAlignment="Stretch" />
</StackPanel>
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="13" />
<TextBlock x:Name="StatusText" Text="Page 1 of 3" Opacity="0.7" TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="6" ClipToBounds="True">
<CarouselPage x:Name="DemoCarousel"
SelectionChanged="OnSelectionChanged">
<ContentPage Header="Welcome">
<StackPanel Margin="24" Spacing="12"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Welcome" FontSize="28" FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBlock Text="Swipe left or use the buttons to navigate."
TextWrapping="Wrap" Opacity="0.7" TextAlignment="Center" MaxWidth="280" />
</StackPanel>
</ContentPage>
<ContentPage Header="Explore">
<StackPanel Margin="24" Spacing="12"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Explore" FontSize="28" FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBlock Text="CarouselPage supports scroll-based and transition-based navigation modes."
TextWrapping="Wrap" Opacity="0.7" TextAlignment="Center" MaxWidth="280" />
</StackPanel>
</ContentPage>
<ContentPage Header="Get Started">
<StackPanel Margin="24" Spacing="12"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Get Started" FontSize="28" FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBlock Text="Use SelectedIndex to jump to any page, or assign a PageTransition for animated switching."
TextWrapping="Wrap" Opacity="0.7" TextAlignment="Center" MaxWidth="280" />
</StackPanel>
</ContentPage>
</CarouselPage>
</Border>
</DockPanel>
</UserControl>

35
samples/ControlCatalog/Pages/CarouselPage/CarouselPageFirstLookPage.xaml.cs

@ -0,0 +1,35 @@
using System.Collections;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselPageFirstLookPage : UserControl
{
public CarouselPageFirstLookPage()
{
InitializeComponent();
}
private void OnPrevious(object? sender, RoutedEventArgs e)
{
if (DemoCarousel.SelectedIndex > 0)
DemoCarousel.SelectedIndex--;
}
private void OnNext(object? sender, RoutedEventArgs e)
{
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
if (DemoCarousel.SelectedIndex < pageCount - 1)
DemoCarousel.SelectedIndex++;
}
private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e)
{
if (StatusText == null)
return;
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
StatusText.Text = $"Page {DemoCarousel.SelectedIndex + 1} of {pageCount}";
}
}
}

64
samples/ControlCatalog/Pages/CarouselPage/CarouselPageGesturePage.xaml

@ -0,0 +1,64 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPageGesturePage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="220">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Gesture Navigation" FontWeight="SemiBold" FontSize="13" />
<CheckBox x:Name="GestureCheck" Content="IsGestureEnabled"
IsChecked="True" IsCheckedChanged="OnGestureChanged" />
<Separator />
<TextBlock Text="Keyboard Navigation" FontWeight="SemiBold" FontSize="13" />
<CheckBox x:Name="KeyboardCheck" Content="IsKeyboardNavigationEnabled"
IsChecked="True" IsCheckedChanged="OnKeyboardChanged" />
<Separator />
<TextBlock Text="Hints" FontWeight="SemiBold" FontSize="13" />
<TextBlock Text="• Swipe left/right to navigate (touch)" FontSize="11" Opacity="0.7" TextWrapping="Wrap" />
<TextBlock Text="• Mouse drag (left button) to navigate" FontSize="11" Opacity="0.7" TextWrapping="Wrap" />
<TextBlock Text="• Arrow keys / Home / End when focused" FontSize="11" Opacity="0.7" TextWrapping="Wrap" />
<TextBlock Text="• Mouse wheel scrolls pages" FontSize="11" Opacity="0.7" TextWrapping="Wrap" />
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="13" />
<TextBlock x:Name="StatusText" Text="—" Opacity="0.7" TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="6" ClipToBounds="True">
<CarouselPage x:Name="DemoCarousel"
SelectionChanged="OnSelectionChanged">
<CarouselPage.PageTransition>
<PageSlide Duration="0:0:0.3" Orientation="Horizontal" />
</CarouselPage.PageTransition>
<ContentPage Header="Page 1" Background="#BBDEFB">
<TextBlock Text="Page 1 — Swipe or drag to navigate"
HorizontalAlignment="Center" VerticalAlignment="Center"
TextWrapping="Wrap" TextAlignment="Center" MaxWidth="200" FontSize="16" />
</ContentPage>
<ContentPage Header="Page 2" Background="#C8E6C9">
<TextBlock Text="Page 2 — Arrow keys also work when focused"
HorizontalAlignment="Center" VerticalAlignment="Center"
TextWrapping="Wrap" TextAlignment="Center" MaxWidth="200" FontSize="16" />
</ContentPage>
<ContentPage Header="Page 3" Background="#FFE0B2">
<TextBlock Text="Page 3 — Disable gestures using the panel"
HorizontalAlignment="Center" VerticalAlignment="Center"
TextWrapping="Wrap" TextAlignment="Center" MaxWidth="200" FontSize="16" />
</ContentPage>
</CarouselPage>
</Border>
</DockPanel>
</UserControl>

44
samples/ControlCatalog/Pages/CarouselPage/CarouselPageGesturePage.xaml.cs

@ -0,0 +1,44 @@
using System.Collections;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselPageGesturePage : UserControl
{
public CarouselPageGesturePage()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object? sender, RoutedEventArgs e) => UpdateStatus();
private void OnGestureChanged(object? sender, RoutedEventArgs e)
{
if (DemoCarousel == null)
return;
DemoCarousel.IsGestureEnabled = GestureCheck.IsChecked == true;
}
private void OnKeyboardChanged(object? sender, RoutedEventArgs e)
{
if (DemoCarousel == null)
return;
DemoCarousel.IsKeyboardNavigationEnabled = KeyboardCheck.IsChecked == true;
}
private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e)
{
UpdateStatus();
}
private void UpdateStatus()
{
if (StatusText == null)
return;
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
StatusText.Text = $"Page {DemoCarousel.SelectedIndex + 1} of {pageCount}";
}
}
}

45
samples/ControlCatalog/Pages/CarouselPage/CarouselPagePerformancePage.xaml

@ -0,0 +1,45 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPagePerformancePage">
<Grid ColumnDefinitions="*,240">
<Border Grid.Column="0" Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<CarouselPage x:Name="DemoCarousel" />
</Border>
<Border Grid.Column="1"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1,0,0,0" Padding="12">
<ScrollViewer>
<StackPanel Spacing="8">
<TextBlock Text="Actions" FontWeight="SemiBold" />
<Button Content="Add 5 Pages" HorizontalAlignment="Stretch" Click="OnAdd5" />
<Button Content="Add 20 Pages" HorizontalAlignment="Stretch" Click="OnAdd20" />
<Button Content="Remove Last 5" HorizontalAlignment="Stretch" Click="OnRemove5" />
<Button Content="Clear All" HorizontalAlignment="Stretch" Click="OnClearAll" />
<Separator />
<Button Content="Force GC" HorizontalAlignment="Stretch" Click="OnForceGC" />
<Button Content="Refresh Stats" HorizontalAlignment="Stretch" Click="OnRefresh" />
<Separator />
<TextBlock Text="Statistics" FontWeight="SemiBold" />
<TextBlock x:Name="PageCountText" Text="Page count: 0" FontSize="12" />
<TextBlock x:Name="LiveCountText" Text="Live instances: 0" FontSize="12" />
<TextBlock x:Name="HeapText" Text="Heap: 0 KB" FontSize="12" />
<TextBlock x:Name="AllocText" Text="Total allocated: 0 KB" FontSize="12" />
<TextBlock x:Name="LastOpTimeText" Text="Last Op: " FontSize="12" />
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</UserControl>

103
samples/ControlCatalog/Pages/CarouselPage/CarouselPagePerformancePage.xaml.cs

@ -0,0 +1,103 @@
using System;
using System.Collections;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace ControlCatalog.Pages
{
public partial class CarouselPagePerformancePage : UserControl
{
private readonly NavigationPerformanceMonitorHelper _perf = new();
private int _counter;
public CarouselPagePerformancePage()
{
InitializeComponent();
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object? sender, RoutedEventArgs e)
{
AddPages(5);
DemoCarousel.SelectionChanged += OnSelectionChanged;
}
private void OnUnloaded(object? sender, RoutedEventArgs e)
{
DemoCarousel.SelectionChanged -= OnSelectionChanged;
}
private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e) => RefreshStats();
private void AddPages(int count)
{
var pages = (IList)DemoCarousel.Pages!;
_perf.OpStopwatch.Restart();
for (int i = 0; i < count; i++)
{
var idx = ++_counter;
var page = new ContentPage
{
Header = $"P{idx}",
Content = new TextBlock
{
Text = $"Page {idx}",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
FontSize = 18,
Opacity = 0.7
},
Tag = new byte[51200],
};
_perf.TrackPage(page);
pages.Add(page);
}
_perf.StopMetrics(LastOpTimeText);
RefreshStats();
}
private void RemovePages(int count)
{
var pages = (IList)DemoCarousel.Pages!;
_perf.OpStopwatch.Restart();
for (int i = 0; i < count && pages.Count > 0; i++)
pages.RemoveAt(pages.Count - 1);
_perf.StopMetrics(LastOpTimeText);
RefreshStats();
}
private void OnAdd5(object? sender, RoutedEventArgs e) => AddPages(5);
private void OnAdd20(object? sender, RoutedEventArgs e) => AddPages(20);
private void OnRemove5(object? sender, RoutedEventArgs e) => RemovePages(5);
private void OnClearAll(object? sender, RoutedEventArgs e)
{
var pages = (IList)DemoCarousel.Pages!;
_perf.OpStopwatch.Restart();
while (pages.Count > 0)
pages.RemoveAt(pages.Count - 1);
_perf.StopMetrics(LastOpTimeText);
RefreshStats();
}
private void OnForceGC(object? sender, RoutedEventArgs e)
{
_perf.ForceGC(RefreshStats);
}
private void OnRefresh(object? sender, RoutedEventArgs e) => RefreshStats();
private void RefreshStats()
{
var pages = (IList)DemoCarousel.Pages!;
PageCountText.Text = $"Page count: {pages.Count}";
LiveCountText.Text = $"Live instances: {_perf.CountLiveInstances()} / {_perf.TotalCreated} tracked";
HeapText.Text = $"Heap: {GC.GetTotalMemory(false) / 1024:N0} KB";
AllocText.Text = $"Total allocated: {GC.GetTotalAllocatedBytes() / 1024:N0} KB";
}
}
}

90
samples/ControlCatalog/Pages/CarouselPage/CarouselPageSelectionPage.xaml

@ -0,0 +1,90 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPageSelectionPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="220">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Jump to Page" FontWeight="SemiBold" FontSize="13" />
<StackPanel Spacing="6">
<Button Content="Go to Page 1" Click="OnGoTo0" HorizontalAlignment="Stretch" />
<Button Content="Go to Page 2" Click="OnGoTo1" HorizontalAlignment="Stretch" />
<Button Content="Go to Page 3" Click="OnGoTo2" HorizontalAlignment="Stretch" />
<Button Content="Go to Page 4" Click="OnGoTo3" HorizontalAlignment="Stretch" />
</StackPanel>
<Separator />
<TextBlock Text="Navigate" FontWeight="SemiBold" FontSize="13" />
<StackPanel Spacing="6">
<Button Content="First" Click="OnFirst" HorizontalAlignment="Stretch" />
<Button Content="Previous" Click="OnPrevious" HorizontalAlignment="Stretch" />
<Button Content="Next" Click="OnNext" HorizontalAlignment="Stretch" />
<Button Content="Last" Click="OnLast" HorizontalAlignment="Stretch" />
</StackPanel>
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="13" />
<TextBlock x:Name="StatusText" Text="—" Opacity="0.7" TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="6" ClipToBounds="True">
<CarouselPage x:Name="DemoCarousel"
SelectionChanged="OnSelectionChanged">
<ContentPage Header="Onboarding" Background="#BBDEFB">
<StackPanel Margin="24" Spacing="8"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Step 1 of 4" FontSize="14" Opacity="0.6"
HorizontalAlignment="Center" />
<TextBlock Text="Onboarding" FontSize="28" FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBlock Text="Set SelectedIndex to jump directly to any page."
TextWrapping="Wrap" Opacity="0.7" TextAlignment="Center" MaxWidth="260" />
</StackPanel>
</ContentPage>
<ContentPage Header="Features" Background="#C8E6C9">
<StackPanel Margin="24" Spacing="8"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Step 2 of 4" FontSize="14" Opacity="0.6"
HorizontalAlignment="Center" />
<TextBlock Text="Features" FontSize="28" FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBlock Text="Supports scroll, transition, swipe gesture and keyboard navigation."
TextWrapping="Wrap" Opacity="0.7" TextAlignment="Center" MaxWidth="260" />
</StackPanel>
</ContentPage>
<ContentPage Header="Customize" Background="#FFE0B2">
<StackPanel Margin="24" Spacing="8"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Step 3 of 4" FontSize="14" Opacity="0.6"
HorizontalAlignment="Center" />
<TextBlock Text="Customize" FontSize="28" FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBlock Text="Override the ItemsPanel, add any Avalonia Page as a child."
TextWrapping="Wrap" Opacity="0.7" TextAlignment="Center" MaxWidth="260" />
</StackPanel>
</ContentPage>
<ContentPage Header="Ready" Background="#E1BEE7">
<StackPanel Margin="24" Spacing="8"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Step 4 of 4" FontSize="14" Opacity="0.6"
HorizontalAlignment="Center" />
<TextBlock Text="Ready!" FontSize="28" FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBlock Text="You are all set. Use SelectionChanged to react to navigation."
TextWrapping="Wrap" Opacity="0.7" TextAlignment="Center" MaxWidth="260" />
</StackPanel>
</ContentPage>
</CarouselPage>
</Border>
</DockPanel>
</UserControl>

56
samples/ControlCatalog/Pages/CarouselPage/CarouselPageSelectionPage.xaml.cs

@ -0,0 +1,56 @@
using System.Collections;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselPageSelectionPage : UserControl
{
public CarouselPageSelectionPage()
{
InitializeComponent();
Loaded += (_, _) => UpdateStatus();
}
private void OnGoTo0(object? sender, RoutedEventArgs e) => DemoCarousel.SelectedIndex = 0;
private void OnGoTo1(object? sender, RoutedEventArgs e) => DemoCarousel.SelectedIndex = 1;
private void OnGoTo2(object? sender, RoutedEventArgs e) => DemoCarousel.SelectedIndex = 2;
private void OnGoTo3(object? sender, RoutedEventArgs e) => DemoCarousel.SelectedIndex = 3;
private void OnFirst(object? sender, RoutedEventArgs e) => DemoCarousel.SelectedIndex = 0;
private void OnPrevious(object? sender, RoutedEventArgs e)
{
if (DemoCarousel.SelectedIndex > 0)
DemoCarousel.SelectedIndex--;
}
private void OnNext(object? sender, RoutedEventArgs e)
{
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
if (DemoCarousel.SelectedIndex < pageCount - 1)
DemoCarousel.SelectedIndex++;
}
private void OnLast(object? sender, RoutedEventArgs e)
{
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
if (pageCount > 0)
DemoCarousel.SelectedIndex = pageCount - 1;
}
private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e)
{
UpdateStatus();
}
private void UpdateStatus()
{
if (StatusText == null)
return;
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
var header = (DemoCarousel.SelectedPage as ContentPage)?.Header?.ToString() ?? "—";
StatusText.Text = $"Page {DemoCarousel.SelectedIndex + 1} of {pageCount}: {header}";
}
}
}

65
samples/ControlCatalog/Pages/CarouselPage/CarouselPageTransitionsPage.xaml

@ -0,0 +1,65 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPageTransitionsPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="220">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Page Transition" FontWeight="SemiBold" FontSize="13" />
<ComboBox x:Name="TransitionCombo" SelectedIndex="0"
SelectionChanged="OnTransitionChanged"
HorizontalAlignment="Stretch">
<ComboBoxItem Content="None" />
<ComboBoxItem Content="CrossFade" />
<ComboBoxItem Content="Slide Horizontal" />
<ComboBoxItem Content="Slide Vertical" />
<ComboBoxItem Content="Card Stack" />
<ComboBoxItem Content="Wave Reveal" />
</ComboBox>
<Separator />
<TextBlock Text="Navigate" FontWeight="SemiBold" FontSize="13" />
<StackPanel Spacing="6">
<Button Content="Previous" Click="OnPrevious" HorizontalAlignment="Stretch" />
<Button Content="Next" Click="OnNext" HorizontalAlignment="Stretch" />
</StackPanel>
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="13" />
<TextBlock x:Name="StatusText" Text="Page 1 of 4 | Transition: None"
Opacity="0.7" TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="6" ClipToBounds="True">
<CarouselPage x:Name="DemoCarousel"
SelectionChanged="OnSelectionChanged">
<ContentPage Header="Page 1" Background="#BBDEFB">
<TextBlock Text="Page 1" FontSize="32" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</ContentPage>
<ContentPage Header="Page 2" Background="#C8E6C9">
<TextBlock Text="Page 2" FontSize="32" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</ContentPage>
<ContentPage Header="Page 3" Background="#FFE0B2">
<TextBlock Text="Page 3" FontSize="32" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</ContentPage>
<ContentPage Header="Page 4" Background="#E1BEE7">
<TextBlock Text="Page 4" FontSize="32" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</ContentPage>
</CarouselPage>
</Border>
</DockPanel>
</UserControl>

69
samples/ControlCatalog/Pages/CarouselPage/CarouselPageTransitionsPage.xaml.cs

@ -0,0 +1,69 @@
using System;
using System.Collections;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Interactivity;
using ControlCatalog.Pages.Transitions;
namespace ControlCatalog.Pages
{
public partial class CarouselPageTransitionsPage : UserControl
{
public CarouselPageTransitionsPage()
{
InitializeComponent();
}
private void OnTransitionChanged(object? sender, SelectionChangedEventArgs e)
{
if (DemoCarousel == null)
return;
DemoCarousel.PageTransition = TransitionCombo?.SelectedIndex switch
{
0 => null,
1 => new CrossFade(TimeSpan.FromMilliseconds(300)),
2 => new PageSlide(TimeSpan.FromMilliseconds(300), PageSlide.SlideAxis.Horizontal),
3 => new PageSlide(TimeSpan.FromMilliseconds(300), PageSlide.SlideAxis.Vertical),
4 => new CardStackPageTransition(TimeSpan.FromMilliseconds(400)),
5 => new WaveRevealPageTransition(TimeSpan.FromMilliseconds(600)),
_ => null
};
UpdateStatus();
}
private void OnPrevious(object? sender, RoutedEventArgs e)
{
if (DemoCarousel.SelectedIndex > 0)
DemoCarousel.SelectedIndex--;
}
private void OnNext(object? sender, RoutedEventArgs e)
{
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
if (DemoCarousel.SelectedIndex < pageCount - 1)
DemoCarousel.SelectedIndex++;
}
private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e)
{
UpdateStatus();
}
private void UpdateStatus()
{
if (StatusText == null)
return;
var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
var modeName = DemoCarousel.PageTransition switch
{
null => "None",
CardStackPageTransition => "Card Stack",
WaveRevealPageTransition => "Wave Reveal",
{ } t => t.GetType().Name
};
StatusText.Text = $"Page {DemoCarousel.SelectedIndex + 1} of {pageCount} | Transition: {modeName}";
}
}
}

97
samples/ControlCatalog/Pages/CarouselPage/CarouselTransitionsPage.xaml

@ -0,0 +1,97 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselTransitionsPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="260">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" />
<Button x:Name="PreviousButton"
Content="Previous"
HorizontalAlignment="Stretch" />
<Button x:Name="NextButton"
Content="Next"
HorizontalAlignment="Stretch" />
<Separator />
<TextBlock Text="Transition" FontWeight="SemiBold" FontSize="13" />
<ComboBox x:Name="TransitionCombo"
HorizontalAlignment="Stretch"
SelectedIndex="1">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Page Slide</ComboBoxItem>
<ComboBoxItem>Cross Fade</ComboBoxItem>
<ComboBoxItem>Rotate 3D</ComboBoxItem>
<ComboBoxItem>Card Stack</ComboBoxItem>
<ComboBoxItem>Wave Reveal</ComboBoxItem>
<ComboBoxItem>Composite (Slide + Fade)</ComboBoxItem>
</ComboBox>
<TextBlock Text="Orientation" FontWeight="SemiBold" FontSize="13" />
<ComboBox x:Name="OrientationCombo"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<ComboBoxItem>Horizontal</ComboBoxItem>
<ComboBoxItem>Vertical</ComboBoxItem>
</ComboBox>
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" />
<TextBlock x:Name="StatusText"
Text="Transition: Page Slide"
Opacity="0.7"
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<Carousel x:Name="DemoCarousel" Height="300">
<Carousel.PageTransition>
<PageSlide Duration="0.25" Orientation="Horizontal" />
</Carousel.PageTransition>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/delicate-arch-896885_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 1: Delicate Arch" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/hirsch-899118_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 2: Hirsch" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
<Border Margin="14,12" CornerRadius="12" ClipToBounds="True">
<Grid>
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" />
<Border Background="#80000000" VerticalAlignment="Bottom" Padding="12">
<TextBlock Text="Item 3: Maple Leaf" Foreground="White"
HorizontalAlignment="Center" FontWeight="SemiBold" />
</Border>
</Grid>
</Border>
</Carousel>
</Border>
</DockPanel>
</UserControl>

66
samples/ControlCatalog/Pages/CarouselPage/CarouselTransitionsPage.xaml.cs

@ -0,0 +1,66 @@
using System;
using Avalonia.Animation;
using Avalonia.Controls;
using ControlCatalog.Pages.Transitions;
namespace ControlCatalog.Pages
{
public partial class CarouselTransitionsPage : UserControl
{
public CarouselTransitionsPage()
{
InitializeComponent();
PreviousButton.Click += (_, _) => DemoCarousel.Previous();
NextButton.Click += (_, _) => DemoCarousel.Next();
TransitionCombo.SelectionChanged += (_, _) => ApplyTransition();
OrientationCombo.SelectionChanged += (_, _) => ApplyTransition();
}
private void ApplyTransition()
{
var axis = OrientationCombo.SelectedIndex == 0 ?
PageSlide.SlideAxis.Horizontal :
PageSlide.SlideAxis.Vertical;
var label = axis == PageSlide.SlideAxis.Horizontal ? "Horizontal" : "Vertical";
switch (TransitionCombo.SelectedIndex)
{
case 0:
DemoCarousel.PageTransition = null;
StatusText.Text = "Transition: None";
break;
case 1:
DemoCarousel.PageTransition = new PageSlide(TimeSpan.FromSeconds(0.25), axis);
StatusText.Text = $"Transition: Page Slide ({label})";
break;
case 2:
DemoCarousel.PageTransition = new CrossFade(TimeSpan.FromSeconds(0.25));
StatusText.Text = "Transition: Cross Fade";
break;
case 3:
DemoCarousel.PageTransition = new Rotate3DTransition(TimeSpan.FromSeconds(0.5), axis);
StatusText.Text = $"Transition: Rotate 3D ({label})";
break;
case 4:
DemoCarousel.PageTransition = new CardStackPageTransition(TimeSpan.FromSeconds(0.5), axis);
StatusText.Text = $"Transition: Card Stack ({label})";
break;
case 5:
DemoCarousel.PageTransition = new WaveRevealPageTransition(TimeSpan.FromSeconds(0.8), axis);
StatusText.Text = $"Transition: Wave Reveal ({label})";
break;
case 6:
DemoCarousel.PageTransition = new CompositePageTransition
{
PageTransitions =
{
new PageSlide(TimeSpan.FromSeconds(0.25), axis),
new CrossFade(TimeSpan.FromSeconds(0.25)),
}
};
StatusText.Text = "Transition: Composite (Slide + Fade)";
break;
}
}
}
}

132
samples/ControlCatalog/Pages/CarouselPage/CarouselVerticalPage.xaml

@ -0,0 +1,132 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselVerticalPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="260">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="Navigation" FontWeight="SemiBold" FontSize="13" />
<Button x:Name="PreviousButton" Content="Up" HorizontalAlignment="Stretch" />
<Button x:Name="NextButton" Content="Down" HorizontalAlignment="Stretch" />
<Separator />
<TextBlock Text="Transition" FontWeight="SemiBold" FontSize="13" />
<ComboBox x:Name="TransitionCombo"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<ComboBoxItem>PageSlide</ComboBoxItem>
<ComboBoxItem>CrossFade</ComboBoxItem>
<ComboBoxItem>None</ComboBoxItem>
</ComboBox>
<Separator />
<TextBlock Text="Options" FontWeight="SemiBold" FontSize="13" />
<CheckBox x:Name="WrapCheck"
Content="Wrap Selection"
IsChecked="False"
IsCheckedChanged="OnWrapSelectionChanged" />
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="14" />
<TextBlock x:Name="StatusText"
Text="Item: 1 / 4"
Opacity="0.7"
TextWrapping="Wrap" />
<TextBlock Text="Use Up/Down arrow keys or buttons to navigate."
FontSize="11"
Opacity="0.5"
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<Carousel x:Name="DemoCarousel"
Focusable="True"
IsSwipeEnabled="True">
<Carousel.PageTransition>
<PageSlide Duration="0.3" Orientation="Vertical" />
</Carousel.PageTransition>
<Border Margin="14,12" CornerRadius="12">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#1A1A2E" Offset="0" />
<GradientStop Color="#3525CD" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10">
<TextBlock Text="01" FontSize="64" FontWeight="Bold"
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="Neon Pulse" FontSize="18" FontWeight="SemiBold"
Foreground="#C3C0FF" HorizontalAlignment="Center" />
<TextBlock Text="Slide down to explore" FontSize="12"
Foreground="#80FFFFFF" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Margin="14,12" CornerRadius="12">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#0C1A1F" Offset="0" />
<GradientStop Color="#0891B2" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10">
<TextBlock Text="02" FontSize="64" FontWeight="Bold"
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="Ephemeral Blue" FontSize="18" FontWeight="SemiBold"
Foreground="#BAF0FA" HorizontalAlignment="Center" />
<TextBlock Text="Vertical PageSlide in action" FontSize="12"
Foreground="#80FFFFFF" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Margin="14,12" CornerRadius="12">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#0A1F18" Offset="0" />
<GradientStop Color="#059669" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10">
<TextBlock Text="03" FontSize="64" FontWeight="Bold"
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="Forest Forms" FontSize="18" FontWeight="SemiBold"
Foreground="#A7F3D0" HorizontalAlignment="Center" />
<TextBlock Text="Swipe up or down on touch screens" FontSize="12"
Foreground="#80FFFFFF" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Margin="14,12" CornerRadius="12">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#1F1208" Offset="0" />
<GradientStop Color="#D97706" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10">
<TextBlock Text="04" FontSize="64" FontWeight="Bold"
Foreground="White" HorizontalAlignment="Center" LetterSpacing="-2" />
<TextBlock Text="Golden Hour" FontSize="18" FontWeight="SemiBold"
Foreground="#FDE68A" HorizontalAlignment="Center" />
<TextBlock Text="Switch transitions in the panel" FontSize="12"
Foreground="#80FFFFFF" HorizontalAlignment="Center" />
</StackPanel>
</Border>
</Carousel>
</Border>
</DockPanel>
</UserControl>

39
samples/ControlCatalog/Pages/CarouselPage/CarouselVerticalPage.xaml.cs

@ -0,0 +1,39 @@
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class CarouselVerticalPage : UserControl
{
public CarouselVerticalPage()
{
InitializeComponent();
PreviousButton.Click += (_, _) => DemoCarousel.Previous();
NextButton.Click += (_, _) => DemoCarousel.Next();
DemoCarousel.SelectionChanged += OnSelectionChanged;
TransitionCombo.SelectionChanged += OnTransitionChanged;
DemoCarousel.Loaded += (_, _) => DemoCarousel.Focus();
}
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
StatusText.Text = $"Item: {DemoCarousel.SelectedIndex + 1} / {DemoCarousel.ItemCount}";
}
private void OnTransitionChanged(object? sender, SelectionChangedEventArgs e)
{
DemoCarousel.PageTransition = TransitionCombo.SelectedIndex switch
{
1 => new CrossFade(System.TimeSpan.FromSeconds(0.3)),
2 => null,
_ => new PageSlide(System.TimeSpan.FromSeconds(0.3), PageSlide.SlideAxis.Vertical),
};
}
private void OnWrapSelectionChanged(object? sender, RoutedEventArgs e)
{
DemoCarousel.WrapSelection = WrapCheck.IsChecked == true;
}
}
}

298
samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml

@ -0,0 +1,298 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.SanctuaryMainPage">
<UserControl.Styles>
<Style Selector="Button.primary /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#f47b25" />
<Setter Property="CornerRadius" Value="{TemplateBinding CornerRadius}" />
</Style>
<Style Selector="Button.primary:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#e0701f" />
</Style>
<Style Selector="Button.primary:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#c9611a" />
</Style>
<Style Selector="Button.glass /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#18FFFFFF" />
<Setter Property="CornerRadius" Value="{TemplateBinding CornerRadius}" />
</Style>
<Style Selector="Button.glass:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#2AFFFFFF" />
</Style>
<Style Selector="Button.glass:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#10FFFFFF" />
</Style>
</UserControl.Styles>
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="220">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Sanctuary" FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.7"
Text="Main landing page with a full-screen hero card, Featured Escapes section, and a bottom TabbedPage navigation. Navigated to from the onboarding carousel." />
<Separator />
<TextBlock Text="Design" FontSize="13" FontWeight="SemiBold" />
<TextBlock FontSize="12" Text="Theme: Dark (#221710)" />
<TextBlock FontSize="12" Text="Primary: #f47b25 (warm orange)" />
<Separator />
<TextBlock Text="Tabs" FontSize="13" FontWeight="SemiBold" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="Home — hero + Featured Escapes cards" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="Explore — destination discovery" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="Saved — bookmarked destinations" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="Profile — account and settings" />
<Separator />
<TextBlock Text="Navigation" FontSize="13" FontWeight="SemiBold" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="Reached via 'Get Started' on the Urban Adventures carousel page. Back stack is cleared on arrival." />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Grid RowDefinitions="Auto,*" Background="#221710">
<!-- Top Nav Bar -->
<Border Grid.Row="0" Background="#DD221710" Padding="16,10,16,10">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
<Panel Width="30" Height="30">
<Ellipse Fill="#f47b25" />
<TextBlock Text="▲" Foreground="White" FontSize="13" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Panel>
<TextBlock Text="Sanctuary" FontSize="18" FontWeight="Bold"
Foreground="White" VerticalAlignment="Center" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
<Border Width="48" Height="48" CornerRadius="24" Background="#18FFFFFF">
<TextBlock Text="⌕" FontSize="20" Foreground="#80FFFFFF"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border Width="48" Height="48" CornerRadius="24" Background="#33f47b25">
<TextBlock Text="◉" FontSize="19" Foreground="#f47b25"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</StackPanel>
</Grid>
</Border>
<!-- Tabbed content -->
<TabbedPage Grid.Row="1"
TabPlacement="Bottom"
SelectedIndex="0">
<TabbedPage.Resources>
<SolidColorBrush x:Key="TabbedPageTabStripBackground">#221710</SolidColorBrush>
<SolidColorBrush x:Key="TabbedPageTabItemHeaderForegroundSelected">#f47b25</SolidColorBrush>
<SolidColorBrush x:Key="TabbedPageTabItemHeaderForegroundUnselected">#50FFFFFF</SolidColorBrush>
<Thickness x:Key="TabbedPageTabItemHeaderPadding">8,10,8,4</Thickness>
</TabbedPage.Resources>
<!-- Home Tab -->
<ContentPage Header="Home"
Background="#221710">
<ContentPage.Icon>
<StreamGeometry>M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z</StreamGeometry>
</ContentPage.Icon>
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<StackPanel>
<!-- Hero Card -->
<Border Margin="12,12,12,8" CornerRadius="16" ClipToBounds="True" Height="380">
<Border.Background>
<ImageBrush Source="avares://ControlCatalog/Assets/Sanctuary/main_hero.jpg"
Stretch="UniformToFill" />
</Border.Background>
<Grid>
<Border ClipToBounds="True" Margin="-1">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.45" Color="#55221710" />
<GradientStop Offset="1" Color="#EE221710" />
</LinearGradientBrush>
</Border.Background>
</Border>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Bottom"
Margin="28,0,28,32" Spacing="14">
<StackPanel HorizontalAlignment="Center" Spacing="2">
<TextBlock Text="Find Your" FontSize="40" FontWeight="ExtraBold"
Foreground="White" TextAlignment="Center" />
<TextBlock Text="Sanctuary" FontSize="40" FontWeight="ExtraBold"
Foreground="#f47b25" TextAlignment="Center" />
</StackPanel>
<TextBlock Text="Embark on a curated journey through the world's most serene and breathtaking natural wonders. Escape the noise and rediscover peace."
FontSize="13" Foreground="#CAffffff"
TextAlignment="Center" TextWrapping="Wrap" MaxWidth="280" />
<StackPanel Spacing="10" HorizontalAlignment="Center">
<Button Classes="primary"
Foreground="#221710" FontWeight="Bold" FontSize="14"
CornerRadius="24" Padding="28,14"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center">
<TextBlock Text="Explore the Collection" Foreground="#221710" FontWeight="Bold" />
</Button>
<Button Classes="glass"
Foreground="White" FontWeight="Bold" FontSize="14"
CornerRadius="24" Padding="28,14"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Center">
<TextBlock Text="⊕" Foreground="White" VerticalAlignment="Center" />
<TextBlock Text="View Map" Foreground="White" FontWeight="Bold" VerticalAlignment="Center" />
</StackPanel>
</Button>
</StackPanel>
</StackPanel>
</Grid>
</Border>
<!-- Featured Escapes Header -->
<Grid Margin="16,8,16,12" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="3">
<TextBlock Text="Featured Escapes" FontSize="20" FontWeight="Bold" Foreground="White" />
<TextBlock Text="Hand-picked destinations for your next retreat"
FontSize="12" Foreground="#80FFFFFF" />
</StackPanel>
<TextBlock Grid.Column="1" Text="See All" FontSize="12" FontWeight="Bold"
Foreground="#f47b25" VerticalAlignment="Bottom" />
</Grid>
<!-- Card 1: Deep Forest -->
<Border Margin="16,0,16,12" CornerRadius="16" ClipToBounds="True" Height="240">
<Border.Background>
<ImageBrush Source="avares://ControlCatalog/Assets/Sanctuary/main_deep_forest.jpg"
Stretch="UniformToFill" />
</Border.Background>
<Grid>
<Border ClipToBounds="True" Margin="-1">
<Border.Background>
<LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
<GradientStop Offset="0" Color="#66000000" />
<GradientStop Offset="0.5" Color="Transparent" />
</LinearGradientBrush>
</Border.Background>
</Border>
<StackPanel VerticalAlignment="Bottom" Margin="16,0,16,16" Spacing="4">
<Border CornerRadius="12" Background="#33f47b25" Padding="8,4" HorizontalAlignment="Left">
<TextBlock Text="FOREST" FontSize="10" FontWeight="Bold" Foreground="#f47b25" />
</Border>
<TextBlock Text="Deep Forest" FontSize="22" FontWeight="Bold" Foreground="White" />
<TextBlock Text="Quiet trails in Oregon, USA" FontSize="12" Foreground="#CCffffff" />
</StackPanel>
</Grid>
</Border>
<!-- Card 2: Arctic Silence -->
<Border Margin="16,0,16,12" CornerRadius="16" ClipToBounds="True" Height="240">
<Border.Background>
<ImageBrush Source="avares://ControlCatalog/Assets/Sanctuary/main_arctic_silence.jpg"
Stretch="UniformToFill" />
</Border.Background>
<Grid>
<Border ClipToBounds="True" Margin="-1">
<Border.Background>
<LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
<GradientStop Offset="0" Color="#66000000" />
<GradientStop Offset="0.5" Color="Transparent" />
</LinearGradientBrush>
</Border.Background>
</Border>
<StackPanel VerticalAlignment="Bottom" Margin="16,0,16,16" Spacing="4">
<Border CornerRadius="12" Background="#33f47b25" Padding="8,4" HorizontalAlignment="Left">
<TextBlock Text="ALPINE" FontSize="10" FontWeight="Bold" Foreground="#f47b25" />
</Border>
<TextBlock Text="Arctic Silence" FontSize="22" FontWeight="Bold" Foreground="White" />
<TextBlock Text="Remote retreats in Svalbard" FontSize="12" Foreground="#CCffffff" />
</StackPanel>
</Grid>
</Border>
<!-- Card 3: Desert Sands -->
<Border Margin="16,0,16,20" CornerRadius="16" ClipToBounds="True" Height="240">
<Border.Background>
<ImageBrush Source="avares://ControlCatalog/Assets/Sanctuary/main_desert_sands.jpg"
Stretch="UniformToFill" />
</Border.Background>
<Grid>
<Border ClipToBounds="True" Margin="-1">
<Border.Background>
<LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
<GradientStop Offset="0" Color="#66000000" />
<GradientStop Offset="0.5" Color="Transparent" />
</LinearGradientBrush>
</Border.Background>
</Border>
<StackPanel VerticalAlignment="Bottom" Margin="16,0,16,16" Spacing="4">
<Border CornerRadius="12" Background="#33f47b25" Padding="8,4" HorizontalAlignment="Left">
<TextBlock Text="DESERT" FontSize="10" FontWeight="Bold" Foreground="#f47b25" />
</Border>
<TextBlock Text="Desert Sands" FontSize="22" FontWeight="Bold" Foreground="White" />
<TextBlock Text="Star gazing in Wadi Rum" FontSize="12" Foreground="#CCffffff" />
</StackPanel>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</ContentPage>
<!-- Explore Tab -->
<ContentPage Header="Explore"
Background="#221710">
<ContentPage.Icon>
<StreamGeometry>M12 10.9c-.61 0-1.1.49-1.1 1.1s.49 1.1 1.1 1.1c.61 0 1.1-.49 1.1-1.1s-.49-1.1-1.1-1.1zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm2.19 12.19L6 18l3.81-8.19L18 6l-3.81 8.19z</StreamGeometry>
</ContentPage.Icon>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"
Spacing="12" Margin="32">
<TextBlock Text="✦" FontSize="48" Foreground="#33FFFFFF" HorizontalAlignment="Center" />
<TextBlock Text="Explore" FontSize="20" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" />
<TextBlock Text="Discover new destinations around the world."
FontSize="13" Foreground="#80FFFFFF"
TextAlignment="Center" TextWrapping="Wrap" />
</StackPanel>
</ContentPage>
<!-- Saved Tab -->
<ContentPage Header="Saved"
Background="#221710">
<ContentPage.Icon>
<StreamGeometry>M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z</StreamGeometry>
</ContentPage.Icon>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"
Spacing="12" Margin="32">
<TextBlock Text="♡" FontSize="48" Foreground="#33FFFFFF" HorizontalAlignment="Center" />
<TextBlock Text="Saved" FontSize="20" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" />
<TextBlock Text="Your saved destinations will appear here."
FontSize="13" Foreground="#80FFFFFF"
TextAlignment="Center" TextWrapping="Wrap" />
</StackPanel>
</ContentPage>
<!-- Profile Tab -->
<ContentPage Header="Profile"
Background="#221710">
<ContentPage.Icon>
<StreamGeometry>M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z</StreamGeometry>
</ContentPage.Icon>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"
Spacing="12" Margin="32">
<TextBlock Text="◉" FontSize="48" Foreground="#33FFFFFF" HorizontalAlignment="Center" />
<TextBlock Text="Profile" FontSize="20" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" />
<TextBlock Text="Your profile and settings will appear here."
FontSize="13" Foreground="#80FFFFFF"
TextAlignment="Center" TextWrapping="Wrap" />
</StackPanel>
</ContentPage>
</TabbedPage>
</Grid>
</DockPanel>
</UserControl>

11
samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml.cs

@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace ControlCatalog.Pages;
public partial class SanctuaryMainPage : UserControl
{
public SanctuaryMainPage()
{
InitializeComponent();
}
}

335
samples/ControlCatalog/Pages/CarouselPage/SanctuaryShowcasePage.xaml

@ -0,0 +1,335 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.SanctuaryShowcasePage">
<UserControl.Styles>
<!-- Orange primary button -->
<Style Selector="Button.primary /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#f47b25" />
<Setter Property="CornerRadius" Value="{TemplateBinding CornerRadius}" />
</Style>
<Style Selector="Button.primary:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#e0701f" />
</Style>
<Style Selector="Button.primary:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#c9611a" />
</Style>
<!-- Glass button (semi-transparent white) -->
<Style Selector="Button.glass /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#18FFFFFF" />
<Setter Property="CornerRadius" Value="{TemplateBinding CornerRadius}" />
</Style>
<Style Selector="Button.glass:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#2AFFFFFF" />
</Style>
<Style Selector="Button.glass:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#10FFFFFF" />
</Style>
</UserControl.Styles>
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="220">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Sanctuary" FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.7"
Text="A travel discovery app with 3 full-screen immersive pages. Uses PipsPager with custom pill-shaped indicators. Swipe, use arrow keys, tap the CTA buttons, or click the pip indicators to navigate." />
<Separator />
<TextBlock Text="Design" FontSize="13" FontWeight="SemiBold" />
<TextBlock FontSize="12" Text="Theme: Dark (#221710)" />
<TextBlock FontSize="12" Text="Primary: #f47b25 (warm orange)" />
<Separator />
<TextBlock Text="Pages" FontSize="13" FontWeight="SemiBold" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="1. Explore the Unknown — mountain backdrop" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="2. Hidden Sanctuaries — forest scene" />
<TextBlock FontSize="12" TextWrapping="Wrap" Text="3. Urban Adventures — neon city" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1" CornerRadius="8" ClipToBounds="True" Margin="12">
<Grid>
<CarouselPage x:Name="DemoCarousel" IsGestureEnabled="True">
<!-- Page 1: Explore the Unknown -->
<ContentPage HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<ContentPage.Background>
<ImageBrush Source="avares://ControlCatalog/Assets/Sanctuary/mountain_bg.jpg"
Stretch="UniformToFill" />
</ContentPage.Background>
<Grid>
<Border>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.35" Color="#55221710" />
<GradientStop Offset="1" Color="#EE221710" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Grid RowDefinitions="*,Auto">
<StackPanel Grid.Row="1" Margin="28,0,28,44" Spacing="0"
HorizontalAlignment="Center">
<TextBlock Text="Explore the Unknown"
FontSize="36" FontWeight="Bold" Foreground="White"
TextAlignment="Center" TextWrapping="Wrap"
MaxWidth="300" Margin="0,0,0,14" />
<TextBlock Text="Embark on an unforgettable adventure through pristine wilderness and discover the hidden wonders of the world's most majestic peaks."
FontSize="14" Foreground="#CAffffff"
TextAlignment="Center" TextWrapping="Wrap"
MaxWidth="300" Margin="0,0,0,24" />
<Button Classes="primary"
Foreground="White" FontWeight="Bold" FontSize="16"
CornerRadius="24" Padding="24,16"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Click="OnPage1CTA" Margin="0,0,0,48">
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Center">
<TextBlock Text="Start Journey" Foreground="White"
FontWeight="Bold" VerticalAlignment="Center" />
<TextBlock Text="→" Foreground="White" VerticalAlignment="Center" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Grid>
</ContentPage>
<!-- Page 2: Hidden Sanctuaries -->
<ContentPage HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<ContentPage.Background>
<ImageBrush Source="avares://ControlCatalog/Assets/Sanctuary/forest_bg.jpg"
Stretch="UniformToFill" />
</ContentPage.Background>
<Grid>
<Border>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#88221710" />
<GradientStop Offset="0.3" Color="Transparent" />
<GradientStop Offset="0.75" Color="#55221710" />
<GradientStop Offset="1" Color="#CC221710" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Grid RowDefinitions="Auto,*,Auto">
<!-- Logo bar -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="8"
Margin="20,22,20,0">
<Panel Width="28" Height="28">
<Ellipse Fill="#f47b25" />
<TextBlock Text="▲" Foreground="White" FontSize="12" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Panel>
<TextBlock Text="SANCTUARY" Foreground="White" FontWeight="Bold"
FontSize="15" VerticalAlignment="Center" />
</StackPanel>
<!-- Center content -->
<StackPanel Grid.Row="1" Margin="24,0,24,0" Spacing="18"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Border CornerRadius="20" BorderBrush="#50f47b25" BorderThickness="1"
Background="#1Af47b25" HorizontalAlignment="Center" Padding="14,6">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="◎" Foreground="#f47b25" FontSize="11" VerticalAlignment="Center" />
<TextBlock Text="VOLUME II: SECLUSION" Foreground="#f47b25"
FontSize="11" FontWeight="Bold" />
</StackPanel>
</Border>
<StackPanel Spacing="0" HorizontalAlignment="Center">
<TextBlock Text="Hidden" Foreground="White"
FontSize="54" FontWeight="Bold" TextAlignment="Center" />
<TextBlock Text="Sanctuaries" Foreground="White"
FontSize="54" FontStyle="Italic" FontWeight="Light"
TextAlignment="Center" />
</StackPanel>
<TextBlock Text="Find your peace in the world's most secluded natural wonders. From misty forest groves to still mountain lakes, discover the quiet beauty of nature's best-kept secrets."
Foreground="#CCffffff" FontSize="14"
TextAlignment="Center" TextWrapping="Wrap" MaxWidth="310" />
<StackPanel Spacing="10" HorizontalAlignment="Center">
<Button Classes="primary"
Foreground="#221710" FontWeight="Bold" FontSize="15"
CornerRadius="24" Padding="32,16"
HorizontalContentAlignment="Center" MinWidth="220"
Click="OnPage2CTA">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="Discover More" Foreground="#221710"
FontWeight="Bold" VerticalAlignment="Center" />
<TextBlock Text="→" Foreground="#221710" VerticalAlignment="Center" />
</StackPanel>
</Button>
<Button Classes="glass"
Foreground="White" FontWeight="Bold" FontSize="15"
CornerRadius="24" Padding="32,16"
HorizontalContentAlignment="Center" MinWidth="220"
Click="OnPage2CTA">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="▷" Foreground="White" VerticalAlignment="Center" />
<TextBlock Text="Experience" Foreground="White"
FontWeight="Bold" VerticalAlignment="Center" />
</StackPanel>
</Button>
</StackPanel>
</StackPanel>
<!-- Footer: location + social -->
<StackPanel Grid.Row="2" Spacing="14" Margin="0,0,0,44">
<Grid ColumnDefinitions="*,Auto" Margin="20,0,20,0">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="5"
VerticalAlignment="Center">
<TextBlock Text="⊙" Foreground="#80FFFFFF" FontSize="11"
VerticalAlignment="Center" />
<TextBlock Text="NORDIC HIGHLANDS" Foreground="#80FFFFFF"
FontSize="10" FontWeight="SemiBold" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="14">
<TextBlock Text="Instagram" Foreground="#80FFFFFF"
FontSize="10" FontWeight="SemiBold" />
<TextBlock Text="Pinterest" Foreground="#80FFFFFF"
FontSize="10" FontWeight="SemiBold" />
</StackPanel>
</Grid>
</StackPanel>
</Grid>
</Grid>
</ContentPage>
<!-- Page 3: Urban Adventures -->
<ContentPage HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<ContentPage.Background>
<ImageBrush Source="avares://ControlCatalog/Assets/Sanctuary/city_bg.jpg"
Stretch="UniformToFill" />
</ContentPage.Background>
<Grid>
<Border>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.35" Color="#55221710" />
<GradientStop Offset="0.6" Color="#99221710" />
<GradientStop Offset="1" Color="#EE221710" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Grid RowDefinitions="*,Auto">
<StackPanel Grid.Row="1" Margin="28,0,28,44" Spacing="0"
HorizontalAlignment="Center">
<TextBlock Text="Urban Adventures"
FontSize="44" FontWeight="Bold" Foreground="White"
TextAlignment="Center" TextWrapping="Wrap"
MaxWidth="320" Margin="0,0,0,14" />
<Border Height="5" Width="88" CornerRadius="3"
Background="#f47b25" HorizontalAlignment="Center"
Margin="0,0,0,18" />
<TextBlock Text="Experience the electric pulse of the city that never sleeps. Explore hidden gems and neon-lit wonders around every corner."
FontSize="14" Foreground="#CAffffff"
TextAlignment="Center" TextWrapping="Wrap"
MaxWidth="300" Margin="0,0,0,24" />
<Button Classes="primary"
Foreground="White" FontWeight="Bold" FontSize="16"
CornerRadius="24" Padding="24,16"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Click="OnPage3CTA" Margin="0,0,0,48">
<TextBlock Text="Get Started" Foreground="White" FontWeight="Bold" />
</Button>
</StackPanel>
</Grid>
</Grid>
</ContentPage>
</CarouselPage>
<PipsPager HorizontalAlignment="Center"
VerticalAlignment="Bottom" Margin="0,0,0,20"
NumberOfPages="3"
SelectedPageIndex="{Binding #DemoCarousel.SelectedIndex}"
IsPreviousButtonVisible="False"
IsNextButtonVisible="False">
<PipsPager.Styles>
<Style Selector="PipsPager /template/ ListBox ListBoxItem">
<Setter Property="Width" Value="38" />
<Setter Property="Height" Value="24" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="2,0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<ControlTemplate>
<Grid Background="Transparent">
<Border Name="Pip"
Width="8" Height="8" CornerRadius="4"
HorizontalAlignment="Center" VerticalAlignment="Center"
Background="#4DFFFFFF">
<Border.Transitions>
<Transitions>
<DoubleTransition Property="Width" Duration="0:0:0.25" Easing="CubicEaseOut" />
<DoubleTransition Property="Height" Duration="0:0:0.25" Easing="CubicEaseOut" />
<CornerRadiusTransition Property="CornerRadius" Duration="0:0:0.25" Easing="CubicEaseOut" />
<BrushTransition Property="Background" Duration="0:0:0.25" />
</Transitions>
</Border.Transitions>
</Border>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:pointerover /template/ Border#Pip">
<Setter Property="Width" Value="10" />
<Setter Property="Height" Value="10" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="Background" Value="#80FFFFFF" />
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:selected /template/ Border#Pip">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="8" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Background" Value="#f47b25" />
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:selected:pointerover /template/ Border#Pip">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="8" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Background" Value="#e0701f" />
</Style>
<Style Selector="PipsPager /template/ ListBox ListBoxItem:pressed /template/ Border#Pip">
<Setter Property="Width" Value="8" />
<Setter Property="Height" Value="8" />
<Setter Property="Background" Value="#f47b25" />
</Style>
</PipsPager.Styles>
</PipsPager>
</Grid>
</Border>
</DockPanel>
</UserControl>

70
samples/ControlCatalog/Pages/CarouselPage/SanctuaryShowcasePage.xaml.cs

@ -0,0 +1,70 @@
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace ControlCatalog.Pages;
public partial class SanctuaryShowcasePage : UserControl
{
public SanctuaryShowcasePage()
{
InitializeComponent();
}
private void OnPage1CTA(object? sender, RoutedEventArgs e)
{
DemoCarousel.SelectedIndex = 1;
}
private void OnPage2CTA(object? sender, RoutedEventArgs e)
{
DemoCarousel.SelectedIndex = 2;
}
private async void OnPage3CTA(object? sender, RoutedEventArgs e)
{
var nav = this.FindAncestorOfType<NavigationPage>();
if (nav == null)
return;
var carouselWrapper = nav.NavigationStack.LastOrDefault();
var headerGrid = new Grid { ColumnDefinitions = new ColumnDefinitions("*, Auto") };
headerGrid.Children.Add(new TextBlock
{
Text = "Sanctuary",
VerticalAlignment = VerticalAlignment.Center
});
var closeIcon = Geometry.Parse(
"M4.397 4.397a1 1 0 0 1 1.414 0L12 10.585l6.19-6.188a1 1 0 0 1 1.414 1.414L13.413 12l6.19 6.189a1 1 0 0 1-1.414 1.414L12 13.413l-6.189 6.19a1 1 0 0 1-1.414-1.414L10.585 12 4.397 5.811a1 1 0 0 1 0-1.414z");
var closeBtn = new Button
{
Content = new PathIcon { Data = closeIcon },
Background = Brushes.Transparent,
BorderThickness = new Thickness(0),
Padding = new Thickness(8, 4),
VerticalAlignment = VerticalAlignment.Center
};
Grid.SetColumn(closeBtn, 1);
headerGrid.Children.Add(closeBtn);
closeBtn.Click += async (_, _) => await nav.PopAsync(null);
var mainPage = new ContentPage
{
Header = headerGrid,
Content = new SanctuaryMainPage()
};
NavigationPage.SetHasBackButton(mainPage, false);
await nav.PushAsync(mainPage);
if (carouselWrapper != null)
{
nav.RemovePage(carouselWrapper);
}
}
}

95
samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml

@ -0,0 +1,95 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CommandBarEventsPage">
<UserControl.Resources>
<StreamGeometry x:Key="AddIcon">M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z</StreamGeometry>
<StreamGeometry x:Key="SaveIcon">M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z</StreamGeometry>
<StreamGeometry x:Key="ShareIcon">M18,16.08C17.24,16.08 16.56,16.38 16.04,16.85L8.91,12.7C8.96,12.47 9,12.24 9,12C9,11.76 8.96,11.53 8.91,11.3L15.96,7.19C16.5,7.69 17.21,8 18,8A3,3 0 0,0 21,5A3,3 0 0,0 18,2A3,3 0 0,0 15,5C15,5.24 15.04,5.47 15.09,5.7L8.04,9.81C7.5,9.31 6.79,9 6,9A3,3 0 0,0 3,12A3,3 0 0,0 6,15C6.79,15 7.5,14.69 8.04,14.19L15.16,18.34C15.11,18.55 15.08,18.77 15.08,19C15.08,20.61 16.39,21.91 18,21.91C19.61,21.91 20.92,20.61 20.92,19C20.92,17.39 19.61,16.08 18,16.08Z</StreamGeometry>
<StreamGeometry x:Key="ExportIcon">M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20M16,11V18.1L13.9,16L11.1,18.8L8.3,16L11.1,13.2L9,11.1L16,11Z</StreamGeometry>
<StreamGeometry x:Key="DeleteIcon">M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z</StreamGeometry>
</UserControl.Resources>
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="280">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Actions" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<CheckBox x:Name="IsOpenCheck"
Content="IsOpen"
IsCheckedChanged="OnIsOpenChanged" />
<Button Content="+ Add Primary"
HorizontalAlignment="Stretch"
Click="OnAddPrimary" />
<Button Content="- Remove Primary"
HorizontalAlignment="Stretch"
Click="OnRemovePrimary" />
<Button Content="+ Add Secondary"
HorizontalAlignment="Stretch"
Click="OnAddSecondary" />
<Button Content="- Remove Secondary"
HorizontalAlignment="Stretch"
Click="OnRemoveSecondary" />
<Button Content="Clear Log"
HorizontalAlignment="Stretch"
Click="OnClearLog" />
<Separator />
<TextBlock Text="State" FontWeight="SemiBold" />
<TextBlock x:Name="StateText"
FontSize="12"
Opacity="0.7"
TextWrapping="Wrap" />
<Separator />
<TextBlock Text="About" FontWeight="SemiBold" />
<TextBlock FontSize="12" Opacity="0.7" TextWrapping="Wrap"
Text="Opening/Opened fire when the overflow opens. Closing/Closed fire when it closes. The log also records item clicks and command execution while the state panel reflects IsOpen, HasSecondaryCommands, and IsOverflowButtonVisible in real time." />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1" Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<ScrollViewer>
<StackPanel Spacing="12" Margin="12,12,12,0">
<TextBlock Classes="h2">Observe CommandBar overflow events, item invocation, and state changes while opening, closing, and editing the primary and secondary command sets.</TextBlock>
<CommandBar x:Name="DemoBar"
OverflowButtonVisibility="Auto">
<CommandBar.PrimaryCommands>
<AppBarButton Label="New"><AppBarButton.Icon><PathIcon Data="{StaticResource AddIcon}" /></AppBarButton.Icon></AppBarButton>
<AppBarButton Label="Save"><AppBarButton.Icon><PathIcon Data="{StaticResource SaveIcon}" /></AppBarButton.Icon></AppBarButton>
<AppBarButton Label="Share"><AppBarButton.Icon><PathIcon Data="{StaticResource ShareIcon}" /></AppBarButton.Icon></AppBarButton>
</CommandBar.PrimaryCommands>
<CommandBar.SecondaryCommands>
<AppBarButton Label="Export"><AppBarButton.Icon><PathIcon Data="{StaticResource ExportIcon}" /></AppBarButton.Icon></AppBarButton>
<AppBarButton Label="Delete"><AppBarButton.Icon><PathIcon Data="{StaticResource DeleteIcon}" /></AppBarButton.Icon></AppBarButton>
</CommandBar.SecondaryCommands>
</CommandBar>
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
Padding="10">
<StackPanel Spacing="6">
<TextBlock Text="Event Log" FontWeight="SemiBold" />
<TextBlock x:Name="EventLogText"
FontFamily="Consolas, Menlo, monospace"
FontSize="12"
TextWrapping="Wrap"
MinHeight="140"
Opacity="0.8"
Text="Ready" />
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</DockPanel>
</UserControl>

220
samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml.cs

@ -0,0 +1,220 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using MiniMvvm;
namespace ControlCatalog.Pages
{
public partial class CommandBarEventsPage : UserControl
{
private readonly List<string> _log = new();
private int _primaryCount = 3;
private int _secondaryCount = 2;
public CommandBarEventsPage()
{
InitializeComponent();
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object? sender, RoutedEventArgs e)
{
DemoBar.Opening += OnOpening;
DemoBar.Opened += OnOpened;
DemoBar.Closing += OnClosing;
DemoBar.Closed += OnClosed;
DemoBar.PropertyChanged += OnBarPropertyChanged;
AttachItemHandlers(DemoBar.PrimaryCommands);
AttachItemHandlers(DemoBar.SecondaryCommands);
AppendLog("Ready");
RefreshState();
}
private void OnUnloaded(object? sender, RoutedEventArgs e)
{
DemoBar.Opening -= OnOpening;
DemoBar.Opened -= OnOpened;
DemoBar.Closing -= OnClosing;
DemoBar.Closed -= OnClosed;
DemoBar.PropertyChanged -= OnBarPropertyChanged;
DetachItemHandlers(DemoBar.PrimaryCommands);
DetachItemHandlers(DemoBar.SecondaryCommands);
}
private void OnIsOpenChanged(object? sender, RoutedEventArgs e)
{
DemoBar.IsOpen = IsOpenCheck.IsChecked == true;
RefreshState();
}
private void OnAddPrimary(object? sender, RoutedEventArgs e)
{
_primaryCount++;
var button = CreateButton($"Primary {_primaryCount}");
DemoBar.PrimaryCommands.Add(button);
AppendLog($"Primary +, {DemoBar.PrimaryCommands.Count}");
RefreshState();
}
private void OnRemovePrimary(object? sender, RoutedEventArgs e)
{
RemoveLastCommand(DemoBar.PrimaryCommands, "Primary");
}
private void OnAddSecondary(object? sender, RoutedEventArgs e)
{
_secondaryCount++;
var button = CreateButton($"Secondary {_secondaryCount}");
DemoBar.SecondaryCommands.Add(button);
AppendLog($"Secondary +, {DemoBar.SecondaryCommands.Count}");
RefreshState();
}
private void OnRemoveSecondary(object? sender, RoutedEventArgs e)
{
RemoveLastCommand(DemoBar.SecondaryCommands, "Secondary");
}
private void OnClearLog(object? sender, RoutedEventArgs e)
{
_log.Clear();
EventLogText.Text = "Log cleared";
}
private void OnOpening(object? sender, RoutedEventArgs e)
{
AppendLog("Opening");
RefreshState();
}
private void OnOpened(object? sender, RoutedEventArgs e)
{
AppendLog("Opened");
RefreshState();
}
private void OnClosing(object? sender, RoutedEventArgs e)
{
AppendLog("Closing");
RefreshState();
}
private void OnClosed(object? sender, RoutedEventArgs e)
{
AppendLog("Closed");
RefreshState();
}
private void OnBarPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == CommandBar.IsOpenProperty
|| e.Property == CommandBar.HasSecondaryCommandsProperty
|| e.Property == CommandBar.IsOverflowButtonVisibleProperty)
{
RefreshState();
}
}
private void RefreshState()
{
StateText.Text =
$"IsOpen: {DemoBar.IsOpen}\n" +
$"HasSecondaryCommands: {DemoBar.HasSecondaryCommands}\n" +
$"IsOverflowButtonVisible: {DemoBar.IsOverflowButtonVisible}\n" +
$"Primary: {DemoBar.PrimaryCommands.Count}\n" +
$"Secondary: {DemoBar.SecondaryCommands.Count}\n" +
$"OverflowItems: {DemoBar.OverflowItems.Count}";
IsOpenCheck.IsChecked = DemoBar.IsOpen;
}
private void OnCommandItemClick(object? sender, RoutedEventArgs e)
{
if (sender is AppBarButton button)
AppendLog($"Click, {button.Label}, {DescribePlacement(button)}");
}
private AppBarButton CreateButton(string label)
{
var button = new AppBarButton
{
Label = label,
Icon = new PathIcon
{
Data = StreamGeometry.Parse("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z")
}
};
AttachItemHandler(button);
return button;
}
private void AttachItemHandlers(IEnumerable<ICommandBarElement> items)
{
foreach (var item in items)
AttachItemHandler(item);
}
private void DetachItemHandlers(IEnumerable<ICommandBarElement> items)
{
foreach (var item in items)
{
if (item is AppBarButton button)
button.Click -= OnCommandItemClick;
}
}
private void AttachItemHandler(ICommandBarElement item)
{
if (item is not AppBarButton button)
return;
button.Click -= OnCommandItemClick;
button.Click += OnCommandItemClick;
button.Command = MiniCommand.Create(() => AppendLog($"Command, {button.Label}, {DescribePlacement(button)}"));
}
private void RemoveLastCommand(IList<ICommandBarElement> items, string bucketName)
{
if (items.Count == 0)
return;
var item = items[^1];
var label = item is AppBarButton button ? button.Label ?? "(unnamed)" : item.GetType().Name;
if (item is AppBarButton appBarButton)
appBarButton.Click -= OnCommandItemClick;
items.RemoveAt(items.Count - 1);
AppendLog($"{bucketName} -, {label}, {items.Count}");
RefreshState();
}
private static string DescribePlacement(AppBarButton button)
{
return button.IsInOverflow ? "overflow" : "primary";
}
private void AppendLog(string message)
{
_log.Add(message);
if (_log.Count > 12)
_log.RemoveAt(0);
EventLogText.Text = string.Join("\n", _log.Select((entry, index) => $"{index + 1,2}. {entry}"));
}
}
}

1
samples/ControlCatalog/Pages/CommandBarPage.xaml.cs

@ -22,6 +22,7 @@ namespace ControlCatalog.Pages
// Features
("Features", "Overflow Menu", "Secondary commands appear in an overflow popup. Configure visibility and sticky behavior.", () => new CommandBarOverflowPage()),
("Features", "Dynamic Overflow", "IsDynamicOverflowEnabled moves primary commands to overflow as space shrinks.", () => new CommandBarDynamicOverflowPage()),
("Features", "Events & State", "Observe Opening, Opened, Closing, and Closed while tracking IsOpen, HasSecondaryCommands, and IsOverflowButtonVisible.", () => new CommandBarEventsPage()),
};
public CommandBarPage()

32
samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml

@ -34,12 +34,32 @@
<Separator />
<TextBlock Text="About" FontWeight="SemiBold" />
<TextBlock Text="• NavigationPage.SetTopCommandBar(page, bar): places a CommandBar inside the navigation bar area at the top."
FontSize="12" Opacity="0.7" TextWrapping="Wrap" />
<TextBlock Text="• NavigationPage.SetBottomCommandBar(page, bar): places a CommandBar below the page content at the bottom."
FontSize="12" Opacity="0.7" TextWrapping="Wrap" />
<TextBlock Text="• Each ContentPage has its own CommandBar. It is replaced when navigating between pages."
FontSize="12" Opacity="0.7" TextWrapping="Wrap" />
<StackPanel Spacing="6">
<Grid ColumnDefinitions="Auto,*">
<TextBlock Text="•" FontSize="12" Opacity="0.7" Margin="0,0,6,0" />
<TextBlock Grid.Column="1"
Text="NavigationPage.SetTopCommandBar(page, bar): places a CommandBar inside the navigation bar area at the top."
FontSize="12"
Opacity="0.7"
TextWrapping="Wrap" />
</Grid>
<Grid ColumnDefinitions="Auto,*">
<TextBlock Text="•" FontSize="12" Opacity="0.7" Margin="0,0,6,0" />
<TextBlock Grid.Column="1"
Text="NavigationPage.SetBottomCommandBar(page, bar): places a CommandBar below the page content at the bottom."
FontSize="12"
Opacity="0.7"
TextWrapping="Wrap" />
</Grid>
<Grid ColumnDefinitions="Auto,*">
<TextBlock Text="•" FontSize="12" Opacity="0.7" Margin="0,0,6,0" />
<TextBlock Grid.Column="1"
Text="Each ContentPage has its own CommandBar. It is replaced when navigating between pages."
FontSize="12"
Opacity="0.7"
TextWrapping="Wrap" />
</Grid>
</StackPanel>
</StackPanel>
</ScrollViewer>

3
samples/ControlCatalog/Pages/DrawerDemoPage.xaml.cs

@ -19,6 +19,9 @@ namespace ControlCatalog.Pages
("Features", "Compact Rail",
"CompactOverlay and CompactInline layout modes: a narrow icon rail is always visible and expands on open. Adjust rail width and open pane width.",
() => new DrawerPageCompactPage()),
("Features", "Breakpoint",
"DrawerBreakpointLength: below the threshold the drawer switches to Overlay mode automatically. Resize the window or adjust the slider to see the layout switch in real time.",
() => new DrawerPageBreakpointPage()),
("Features", "Events",
"Opened, Closing, and Closed drawer events plus NavigatedTo and NavigatedFrom page lifecycle events. Enable 'Cancel next close' to prevent the drawer from closing.",
() => new DrawerPageEventsPage()),

113
samples/ControlCatalog/Pages/DrawerPage/DrawerPageBreakpointPage.xaml

@ -0,0 +1,113 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.DrawerPageBreakpointPage">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="240">
<StackPanel Margin="12" Spacing="8">
<TextBlock Text="Configuration" FontWeight="SemiBold" FontSize="16"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock Text="DrawerBreakpointLength" FontSize="13" FontWeight="SemiBold" />
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.7"
Text="When set, the drawer automatically switches layout based on the available size (width for Left/Right, height for Top/Bottom)." />
<Border Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
CornerRadius="4" Padding="8">
<StackPanel Spacing="4">
<Grid ColumnDefinitions="18,*">
<PathIcon Grid.Column="0" Width="12" Height="12" Opacity="0.7" VerticalAlignment="Top" Margin="0,1,0,0"
Data="M19,11H7.83L12.42,6.41L11,5L4,12L11,19L12.41,17.59L7.83,13H19V11Z" />
<TextBlock Grid.Column="1" FontSize="11" TextWrapping="Wrap"
Text="Below breakpoint → Overlay (hamburger button, drawer closes)" />
</Grid>
<Grid ColumnDefinitions="18,*">
<PathIcon Grid.Column="0" Width="12" Height="12" Opacity="0.7" VerticalAlignment="Top" Margin="0,1,0,0"
Data="M5,13H16.17L11.58,17.59L13,19L20,12L13,5L11.59,6.41L16.17,11H5V13Z" />
<TextBlock Grid.Column="1" FontSize="11" TextWrapping="Wrap"
Text="Above breakpoint → configured layout, drawer opens automatically" />
</Grid>
</StackPanel>
</Border>
<TextBlock Text="Breakpoint value" FontSize="12" Opacity="0.7" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Slider x:Name="BreakpointSlider" Minimum="100" Maximum="900" Value="500"
Width="150" ValueChanged="OnBreakpointChanged" />
<TextBlock x:Name="BreakpointText" Text="500" VerticalAlignment="Center" />
</StackPanel>
<Separator />
<TextBlock Text="Layout above breakpoint" FontSize="13" FontWeight="SemiBold" />
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.7"
Text="The DrawerLayoutBehavior used when width exceeds the breakpoint." />
<ComboBox x:Name="LayoutCombo" SelectedIndex="0"
SelectionChanged="OnLayoutChanged" HorizontalAlignment="Stretch">
<ComboBoxItem Content="Split" />
<ComboBoxItem Content="CompactInline" />
<ComboBoxItem Content="CompactOverlay" />
</ComboBox>
<Separator />
<TextBlock Text="Status" FontWeight="SemiBold" FontSize="13" />
<TextBlock x:Name="WidthText" Text="Width: —" Opacity="0.7" />
<TextBlock x:Name="ModeText" Text="Mode: Overlay" Opacity="0.7" TextWrapping="Wrap" />
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.6"
Text="Resize the window to see the layout switch automatically." />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right" Width="1" Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<DrawerPage x:Name="DemoDrawer"
DrawerLayoutBehavior="Split"
DrawerBreakpointLength="500"
DrawerLength="200">
<DrawerPage.Drawer>
<StackPanel Margin="8" Spacing="4">
<Button HorizontalAlignment="Stretch" Background="Transparent"
Click="OnMenuItemClick" Tag="Home">
<StackPanel Orientation="Horizontal" Spacing="8">
<PathIcon Width="16" Height="16"
Data="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" />
<TextBlock Text="Home" VerticalAlignment="Center" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" Background="Transparent"
Click="OnMenuItemClick" Tag="Profile">
<StackPanel Orientation="Horizontal" Spacing="8">
<PathIcon Width="16" Height="16"
Data="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />
<TextBlock Text="Profile" VerticalAlignment="Center" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" Background="Transparent"
Click="OnMenuItemClick" Tag="Settings">
<StackPanel Orientation="Horizontal" Spacing="8">
<PathIcon Width="16" Height="16"
Data="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.04 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.68 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.04 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" />
<TextBlock Text="Settings" VerticalAlignment="Center" />
</StackPanel>
</Button>
</StackPanel>
</DrawerPage.Drawer>
<DrawerPage.Content>
<ContentPage x:Name="DetailPage" Header="Home">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="8">
<TextBlock x:Name="DetailTitleText" Text="Home" FontSize="24" FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBlock Text="Select an item from the drawer." FontSize="13" Opacity="0.7"
TextWrapping="Wrap" TextAlignment="Center" MaxWidth="260" />
</StackPanel>
</ContentPage>
</DrawerPage.Content>
</DrawerPage>
</Border>
</DockPanel>
</UserControl>

84
samples/ControlCatalog/Pages/DrawerPage/DrawerPageBreakpointPage.xaml.cs

@ -0,0 +1,84 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class DrawerPageBreakpointPage : UserControl
{
private bool _isLoaded;
public DrawerPageBreakpointPage()
{
InitializeComponent();
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
_isLoaded = true;
DemoDrawer.PropertyChanged += OnDrawerPropertyChanged;
UpdateStatus();
}
protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(e);
DemoDrawer.PropertyChanged -= OnDrawerPropertyChanged;
}
private void OnDrawerPropertyChanged(object? sender, Avalonia.AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == DrawerPage.BoundsProperty)
UpdateStatus();
}
private void OnBreakpointChanged(object? sender, RangeBaseValueChangedEventArgs e)
{
if (!_isLoaded)
return;
var value = (int)e.NewValue;
DemoDrawer.DrawerBreakpointLength = value;
BreakpointText.Text = value.ToString();
UpdateStatus();
}
private void OnLayoutChanged(object? sender, SelectionChangedEventArgs e)
{
if (!_isLoaded)
return;
DemoDrawer.DrawerLayoutBehavior = LayoutCombo.SelectedIndex switch
{
0 => DrawerLayoutBehavior.Split,
1 => DrawerLayoutBehavior.CompactInline,
2 => DrawerLayoutBehavior.CompactOverlay,
_ => DrawerLayoutBehavior.Split
};
UpdateStatus();
}
private void OnMenuItemClick(object? sender, RoutedEventArgs e)
{
if (!_isLoaded || sender is not Button button)
return;
var item = button.Tag?.ToString() ?? "Home";
DetailTitleText.Text = item;
DetailPage.Header = item;
if (DemoDrawer.DrawerLayoutBehavior != DrawerLayoutBehavior.Split)
DemoDrawer.IsOpen = false;
}
private void UpdateStatus()
{
var isVertical = DemoDrawer.DrawerPlacement == DrawerPlacement.Top ||
DemoDrawer.DrawerPlacement == DrawerPlacement.Bottom;
var length = isVertical ? DemoDrawer.Bounds.Height : DemoDrawer.Bounds.Width;
var breakpoint = DemoDrawer.DrawerBreakpointLength;
WidthText.Text = $"{(isVertical ? "Height" : "Width")}: {(int)length} px";
var isOverlay = breakpoint > 0 && length > 0 && length < breakpoint;
ModeText.Text = isOverlay ?
"Mode: Overlay (below breakpoint)" :
$"Mode: {DemoDrawer.DrawerLayoutBehavior} (above breakpoint)";
}
}
}

5
samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml

@ -118,6 +118,11 @@
Header="Customization"
DrawerLength="260"
DrawerHeaderBackground="{DynamicResource SystemControlHighlightAccentBrush}">
<DrawerPage.DrawerIconTemplate>
<DataTemplate DataType="Geometry">
<PathIcon Data="{Binding}" />
</DataTemplate>
</DrawerPage.DrawerIconTemplate>
<DrawerPage.DrawerHeader>
<Border x:Name="DrawerHeaderBorder" Padding="16">
<StackPanel Spacing="4">

13
samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml.cs

@ -1,5 +1,7 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity;
using Avalonia.Media;
@ -22,6 +24,7 @@ namespace ControlCatalog.Pages
public DrawerPageCustomizationPage()
{
InitializeComponent();
EnableMouseSwipeGesture(DemoDrawer);
}
protected override void OnLoaded(RoutedEventArgs e)
@ -188,5 +191,15 @@ namespace ControlCatalog.Pages
if (DemoDrawer.DrawerBehavior != DrawerBehavior.Locked)
DemoDrawer.IsOpen = false;
}
private static void EnableMouseSwipeGesture(Control control)
{
var recognizer = control.GestureRecognizers
.OfType<SwipeGestureRecognizer>()
.FirstOrDefault();
if (recognizer is not null)
recognizer.IsMouseEnabled = true;
}
}
}

13
samples/ControlCatalog/Pages/DrawerPage/DrawerPageFirstLookPage.xaml.cs

@ -1,4 +1,6 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
@ -8,6 +10,7 @@ namespace ControlCatalog.Pages
public DrawerPageFirstLookPage()
{
InitializeComponent();
EnableMouseSwipeGesture(DemoDrawer);
}
protected override void OnLoaded(RoutedEventArgs e)
@ -61,5 +64,15 @@ namespace ControlCatalog.Pages
{
StatusText.Text = $"Drawer: {(DemoDrawer.IsOpen ? "Open" : "Closed")}";
}
private static void EnableMouseSwipeGesture(Control control)
{
var recognizer = control.GestureRecognizers
.OfType<SwipeGestureRecognizer>()
.FirstOrDefault();
if (recognizer is not null)
recognizer.IsMouseEnabled = true;
}
}
}

8
samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml

@ -52,9 +52,13 @@
</DrawerPage.Resources>
<DrawerPage.DrawerIcon>
<PathIcon Width="22" Height="22"
Data="M12 3C9 6 6 9 6 13C6 17.4 8.7 21 12 22C15.3 21 18 17.4 18 13C18 9 15 6 12 3Z" />
<StreamGeometry>M12 3C9 6 6 9 6 13C6 17.4 8.7 21 12 22C15.3 21 18 17.4 18 13C18 9 15 6 12 3Z</StreamGeometry>
</DrawerPage.DrawerIcon>
<DrawerPage.DrawerIconTemplate>
<DataTemplate DataType="Geometry">
<PathIcon Width="22" Height="22" Data="{Binding}" />
</DataTemplate>
</DrawerPage.DrawerIconTemplate>
<DrawerPage.DrawerHeader>
<StackPanel Background="{StaticResource EcoDrawerBg}" Margin="0,0,0,8">

3
samples/ControlCatalog/Pages/NavigationDemoPage.xaml.cs

@ -28,6 +28,9 @@ namespace ControlCatalog.Pages
// Data
("Data", "Pass Data", "Pass data during navigation via constructor arguments or DataContext.",
() => new NavigationPagePassDataPage()),
("Data", "MVVM Navigation",
"Keep navigation decisions in view models by routing NavigationPage push and pop operations through a small INavigationService.",
() => new NavigationPageMvvmPage()),
// Features
("Features", "Attached Methods",

51
samples/ControlCatalog/Pages/NavigationPage/LAvenirAppPage.xaml.cs

@ -59,6 +59,26 @@ public partial class LAvenirAppPage : UserControl
_infoPanel.IsVisible = Bounds.Width >= 650;
}
void ApplyRootNavigationBarAppearance()
{
if (_navPage == null)
return;
_navPage.Background = new SolidColorBrush(BgLight);
_navPage.Resources["NavigationBarBackground"] = new SolidColorBrush(BgLight);
_navPage.Resources["NavigationBarForeground"] = new SolidColorBrush(TextDark);
}
void ApplyDetailNavigationBarAppearance()
{
if (_navPage == null)
return;
_navPage.Background = new SolidColorBrush(BgDark);
_navPage.Resources["NavigationBarBackground"] = new SolidColorBrush(BgDark);
_navPage.Resources["NavigationBarForeground"] = Brushes.White;
}
TabbedPage BuildMenuTabbedPage()
{
var tp = new TabbedPage
@ -92,6 +112,7 @@ public partial class LAvenirAppPage : UserControl
VerticalAlignment = VerticalAlignment.Center,
TextAlignment = TextAlignment.Center,
};
ApplyRootNavigationBarAppearance();
NavigationPage.SetTopCommandBar(tp, new Button
{
@ -119,7 +140,7 @@ public partial class LAvenirAppPage : UserControl
Content = menuView,
Background = new SolidColorBrush(BgLight),
Header = "Menu",
Icon = "M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z",
Icon = Geometry.Parse("M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z"),
};
var reservationsPage = new ContentPage
@ -127,7 +148,7 @@ public partial class LAvenirAppPage : UserControl
Content = new LAvenirReservationsView(),
Background = new SolidColorBrush(BgLight),
Header = "Reservations",
Icon = "M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM9 10H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2z",
Icon = Geometry.Parse("M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM9 10H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2z"),
};
var profilePage = new ContentPage
@ -135,7 +156,7 @@ public partial class LAvenirAppPage : UserControl
Content = new LAvenirProfileView(),
Background = new SolidColorBrush(BgLight),
Header = "Profile",
Icon = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z",
Icon = Geometry.Parse("M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"),
};
tp.Pages = new ObservableCollection<Page> { menuPage, reservationsPage, profilePage };
@ -144,7 +165,8 @@ public partial class LAvenirAppPage : UserControl
async void PushDishDetail(string name, string price, string description, string imageFile)
{
if (_navPage == null) return;
if (_navPage == null)
return;
var detail = new ContentPage
{
@ -153,22 +175,19 @@ public partial class LAvenirAppPage : UserControl
Header = name,
};
NavigationPage.SetBottomCommandBar(detail, BuildFloatingBar(price));
_navPage.Background = new SolidColorBrush(BgDark);
_navPage.Resources["NavigationBarBackground"] = new SolidColorBrush(BgDark);
_navPage.Resources["NavigationBarForeground"] = Brushes.White;
detail.NavigatedFrom += (_, _) =>
detail.Navigating += args =>
{
if (_navPage != null)
{
_navPage.Background = new SolidColorBrush(BgLight);
_navPage.Resources["NavigationBarBackground"] = new SolidColorBrush(BgLight);
_navPage.Resources["NavigationBarForeground"] = new SolidColorBrush(TextDark);
}
if (args.NavigationType == NavigationType.Pop)
ApplyRootNavigationBarAppearance();
return Task.CompletedTask;
};
ApplyDetailNavigationBarAppearance();
await _navPage.PushAsync(detail);
if (!ReferenceEquals(_navPage.CurrentPage, detail))
ApplyRootNavigationBarAppearance();
}
Border BuildFloatingBar(string price)

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageAppearancePage.xaml.cs

@ -8,6 +8,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageAppearancePage : UserControl
{
private bool _initialized;
private int _pageCount;
private int _backButtonStyle;
@ -19,6 +20,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
await DemoNav.PushAsync(NavigationDemoHelper.MakePage("Appearance", "Change bar properties using the options panel.", 0), null);
}

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageAttachedMethodsPage.xaml.cs

@ -8,6 +8,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageAttachedMethodsPage : UserControl
{
private bool _initialized;
private int _pageCount;
public NavigationPageAttachedMethodsPage()
@ -18,6 +19,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
await DemoNav.PushAsync(new ContentPage
{
Header = "Root Page",

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageBackButtonPage.xaml.cs

@ -8,6 +8,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageBackButtonPage : UserControl
{
private bool _initialized;
private int _pushCount;
public NavigationPageBackButtonPage()
@ -18,6 +19,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
DemoNav.Pushed += (s, ev) => AddLog($"Pushed: \"{ev.Page?.Header}\"");
DemoNav.Popped += (s, ev) => AddLog($"Popped: \"{ev.Page?.Header}\"");

7
samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml

@ -51,6 +51,13 @@
</ScrollViewer>
</Border>
<Separator />
<!-- IsNavigating indicator -->
<TextBlock Text="Status" FontSize="13" FontWeight="SemiBold" />
<TextBlock Text="{Binding IsNavigating, ElementName=DemoNav, StringFormat='IsNavigating: {0}'}"
FontSize="12" />
<Separator />
<TextBlock Text="NavigationPage exposes events for push, pop, insert, remove, and modal operations. Pages also fire NavigatedTo and NavigatedFrom on each navigation."
TextWrapping="Wrap" FontSize="12" Opacity="0.7" />

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml.cs

@ -7,6 +7,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageEventsPage : UserControl
{
private bool _initialized;
private int _pageCount;
public NavigationPageEventsPage()
@ -17,6 +18,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
DemoNav.Pushed += (s, ev) => AddLog($"Pushed → {ev.Page?.Header}");
DemoNav.Popped += (s, ev) => AddLog($"Popped ← {ev.Page?.Header}");
DemoNav.PoppedToRoot += (s, ev) => AddLog("PoppedToRoot");

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageFirstLookPage.xaml.cs

@ -6,6 +6,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageFirstLookPage : UserControl
{
private bool _initialized;
private int _pageCount;
public NavigationPageFirstLookPage()
@ -16,6 +17,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
await DemoNav.PushAsync(NavigationDemoHelper.MakePage("Home", "Welcome!\nUse the buttons to push and pop pages.", 0), null);
UpdateStatus();
}

19
samples/ControlCatalog/Pages/NavigationPage/NavigationPageGesturePage.xaml.cs

@ -1,18 +1,27 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class NavigationPageGesturePage : UserControl
{
private bool _initialized;
public NavigationPageGesturePage()
{
InitializeComponent();
EnableMouseSwipeGesture(DemoNav);
Loaded += OnLoaded;
}
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
await DemoNav.PushAsync(NavigationDemoHelper.MakePage("Page 1", "← Drag from the left edge to go back", 0), null);
await DemoNav.PushAsync(NavigationDemoHelper.MakePage("Page 2", "← Drag from the left edge to go back", 1), null);
await DemoNav.PushAsync(NavigationDemoHelper.MakePage("Page 3", "← Drag from the left edge to go back", 2), null);
@ -43,5 +52,15 @@ namespace ControlCatalog.Pages
{
StatusText.Text = $"Depth: {DemoNav.StackDepth}";
}
private static void EnableMouseSwipeGesture(Control control)
{
var recognizer = control.GestureRecognizers
.OfType<SwipeGestureRecognizer>()
.FirstOrDefault();
if (recognizer is not null)
recognizer.IsMouseEnabled = true;
}
}
}

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageInteractiveHeaderPage.xaml.cs

@ -37,6 +37,7 @@ namespace ControlCatalog.Pages
];
private readonly ObservableCollection<ContactItem> _filteredItems = new(AllContacts);
private bool _initialized;
private string _searchText = "";
public NavigationPageInteractiveHeaderPage()
@ -47,6 +48,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
var headerGrid = new Grid
{
ColumnDefinitions = new ColumnDefinitions("*, Auto"),

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalPage.xaml.cs

@ -7,6 +7,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageModalPage : UserControl
{
private bool _initialized;
private int _modalCount;
public NavigationPageModalPage()
@ -17,6 +18,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
await DemoNav.PushAsync(NavigationDemoHelper.MakePage("Home", "Use Push Modal to show a modal on top.", 0), null);
}

8
samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalTransitionsPage.xaml.cs

@ -20,6 +20,7 @@ namespace ControlCatalog.Pages
];
private int _modalCount;
private bool _initialized;
public NavigationPageModalTransitionsPage()
{
@ -29,6 +30,13 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
{
UpdateTransition();
return;
}
_initialized = true;
await DemoNav.PushAsync(new ContentPage
{
Header = "Modal Transitions",

95
samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmNavigation.cs

@ -0,0 +1,95 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;
using MiniMvvm;
namespace ControlCatalog.Pages
{
internal interface ISampleNavigationService
{
event EventHandler<NavigationStateChangedEventArgs>? StateChanged;
Task NavigateToAsync(ViewModelBase viewModel);
Task GoBackAsync();
Task PopToRootAsync();
}
internal interface ISamplePageFactory
{
ContentPage CreatePage(ViewModelBase viewModel);
}
internal sealed class NavigationStateChangedEventArgs : EventArgs
{
public NavigationStateChangedEventArgs(string currentPageHeader, int navigationDepth, string lastAction)
{
CurrentPageHeader = currentPageHeader;
NavigationDepth = navigationDepth;
LastAction = lastAction;
}
public string CurrentPageHeader { get; }
public int NavigationDepth { get; }
public string LastAction { get; }
}
internal sealed class SampleNavigationService : ISampleNavigationService
{
private readonly NavigationPage _navigationPage;
private readonly ISamplePageFactory _pageFactory;
public SampleNavigationService(NavigationPage navigationPage, ISamplePageFactory pageFactory)
{
_navigationPage = navigationPage;
_pageFactory = pageFactory;
_navigationPage.Pushed += (_, e) => PublishState($"Pushed {e.Page?.Header}");
_navigationPage.Popped += (_, e) => PublishState($"Popped {e.Page?.Header}");
_navigationPage.PoppedToRoot += (_, _) => PublishState("Popped to root");
}
public event EventHandler<NavigationStateChangedEventArgs>? StateChanged;
public async Task NavigateToAsync(ViewModelBase viewModel)
{
var page = _pageFactory.CreatePage(viewModel);
await _navigationPage.PushAsync(page);
}
public async Task GoBackAsync()
{
if (_navigationPage.NavigationStack.Count <= 1)
{
PublishState("Already at the root page");
return;
}
await _navigationPage.PopAsync();
}
public async Task PopToRootAsync()
{
if (_navigationPage.NavigationStack.Count <= 1)
{
PublishState("Already at the root page");
return;
}
await _navigationPage.PopToRootAsync();
}
private void PublishState(string lastAction)
{
var header = _navigationPage.CurrentPage?.Header?.ToString() ?? "None";
StateChanged?.Invoke(this, new NavigationStateChangedEventArgs(
header,
_navigationPage.NavigationStack.Count,
lastAction));
}
}
}

86
samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmPage.xaml

@ -0,0 +1,86 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="using:ControlCatalog.Pages"
x:Class="ControlCatalog.Pages.NavigationPageMvvmPage"
x:DataType="pages:NavigationPageMvvmShellViewModel">
<DockPanel>
<ScrollViewer DockPanel.Dock="Right" Width="300">
<StackPanel Margin="12" Spacing="12">
<TextBlock Text="MVVM Pattern"
FontSize="16"
FontWeight="SemiBold"
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock TextWrapping="Wrap"
FontSize="12"
Opacity="0.75"
Text="This sample keeps NavigationPage in the view, sends commands from view models, routes push or pop operations through ISampleNavigationService, and resolves pages in a separate SamplePageFactory." />
<Separator />
<TextBlock Text="Selected Project"
FontSize="13"
FontWeight="SemiBold" />
<ListBox ItemsSource="{Binding Projects}"
SelectedItem="{Binding SelectedProject, Mode=TwoWay}"
MaxHeight="180">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="pages:ProjectCardViewModel">
<StackPanel Margin="0,2" Spacing="2">
<TextBlock Text="{Binding Name}"
FontWeight="SemiBold" />
<TextBlock Text="{Binding Owner}"
FontSize="11"
Opacity="0.65" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Open Selected Project"
Command="{Binding OpenSelectedProjectCommand}" />
<Button Content="Back"
Command="{Binding GoBackCommand}" />
<Button Content="Pop To Root"
Command="{Binding PopToRootCommand}" />
<Separator />
<TextBlock Text="Navigation State"
FontSize="13"
FontWeight="SemiBold" />
<TextBlock Text="{Binding CurrentPageHeader, StringFormat=Current page: {0}}"
Opacity="0.75" />
<TextBlock Text="{Binding NavigationDepth, StringFormat=Stack depth: {0}}"
Opacity="0.75" />
<TextBlock Text="{Binding LastAction, StringFormat=Last action: {0}}"
Opacity="0.75"
TextWrapping="Wrap" />
<Separator />
<TextBlock Text="Selected Details"
FontSize="13"
FontWeight="SemiBold" />
<TextBlock Text="{Binding SelectedProject.Status, StringFormat=Status: {0}}"
Opacity="0.75" />
<TextBlock Text="{Binding SelectedProject.NextMilestone, StringFormat=Next milestone: {0}}"
Opacity="0.75"
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Right"
Width="1"
Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Border Margin="12"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="6"
ClipToBounds="True">
<NavigationPage x:Name="DemoNav" />
</Border>
</DockPanel>
</UserControl>

32
samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmPage.xaml.cs

@ -0,0 +1,32 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class NavigationPageMvvmPage : UserControl
{
private readonly NavigationPageMvvmShellViewModel _viewModel;
private bool _initialized;
public NavigationPageMvvmPage()
{
InitializeComponent();
ISamplePageFactory pageFactory = new SamplePageFactory();
ISampleNavigationService navigationService = new SampleNavigationService(DemoNav, pageFactory);
_viewModel = new NavigationPageMvvmShellViewModel(navigationService);
DataContext = _viewModel;
Loaded += OnLoaded;
}
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
await _viewModel.InitializeAsync();
}
}
}

252
samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmPageFactory.cs

@ -0,0 +1,252 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Media;
using MiniMvvm;
namespace ControlCatalog.Pages
{
internal sealed class SamplePageFactory : ISamplePageFactory
{
public ContentPage CreatePage(ViewModelBase viewModel) =>
viewModel switch
{
WorkspaceViewModel workspace => CreateWorkspacePage(workspace),
ProjectDetailViewModel detail => CreateProjectDetailPage(detail),
ProjectActivityViewModel activity => CreateProjectActivityPage(activity),
_ => throw new InvalidOperationException($"Unsupported view model: {viewModel.GetType().Name}")
};
private static ContentPage CreateWorkspacePage(WorkspaceViewModel viewModel)
{
var stack = new StackPanel
{
Margin = new Thickness(20),
Spacing = 14,
};
stack.Children.Add(new TextBlock
{
Text = viewModel.Title,
FontSize = 24,
FontWeight = FontWeight.Bold,
});
stack.Children.Add(new TextBlock
{
Text = viewModel.Description,
FontSize = 13,
Opacity = 0.75,
TextWrapping = TextWrapping.Wrap,
});
stack.Children.Add(new ItemsControl
{
ItemsSource = viewModel.Projects,
ItemTemplate = new FuncDataTemplate<ProjectCardViewModel>((item, _) =>
{
if (item == null)
return new TextBlock();
var accentBrush = new SolidColorBrush(item.AccentColor);
var statusBadge = new Border
{
Background = accentBrush,
CornerRadius = new CornerRadius(999),
Padding = new Thickness(10, 4),
Child = new TextBlock
{
Text = item.Status,
Foreground = Brushes.White,
FontSize = 11,
FontWeight = FontWeight.SemiBold,
}
};
DockPanel.SetDock(statusBadge, Dock.Right);
var header = new DockPanel();
header.Children.Add(statusBadge);
header.Children.Add(new TextBlock
{
Text = item.Name,
FontSize = 17,
FontWeight = FontWeight.SemiBold,
});
return new Border
{
Background = new SolidColorBrush(Color.FromArgb(20, item.AccentColor.R, item.AccentColor.G, item.AccentColor.B)),
BorderBrush = accentBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(14),
Margin = new Thickness(0, 0, 0, 8),
Child = new StackPanel
{
Spacing = 8,
Children =
{
header,
new TextBlock
{
Text = item.Summary,
FontSize = 13,
Opacity = 0.72,
TextWrapping = TextWrapping.Wrap,
},
new TextBlock
{
Text = $"Owner: {item.Owner} • Next: {item.NextMilestone}",
FontSize = 12,
Opacity = 0.6,
TextWrapping = TextWrapping.Wrap,
},
new Button
{
Content = "Open Project",
HorizontalAlignment = HorizontalAlignment.Left,
Command = item.OpenCommand,
}
}
}
};
})
});
var page = new ContentPage
{
Header = "Workspace",
Content = new ScrollViewer { Content = stack },
HorizontalContentAlignment = HorizontalAlignment.Stretch,
VerticalContentAlignment = VerticalAlignment.Stretch,
};
NavigationPage.SetHasBackButton(page, false);
return page;
}
private static ContentPage CreateProjectDetailPage(ProjectDetailViewModel viewModel)
{
var accentBrush = new SolidColorBrush(viewModel.AccentColor);
var panel = new StackPanel
{
Margin = new Thickness(24, 20),
Spacing = 12,
};
panel.Children.Add(new Border
{
Background = accentBrush,
CornerRadius = new CornerRadius(999),
Padding = new Thickness(12, 5),
HorizontalAlignment = HorizontalAlignment.Left,
Child = new TextBlock
{
Text = viewModel.Status,
Foreground = Brushes.White,
FontSize = 11,
FontWeight = FontWeight.SemiBold,
}
});
panel.Children.Add(new TextBlock
{
Text = viewModel.Name,
FontSize = 26,
FontWeight = FontWeight.Bold,
});
panel.Children.Add(new TextBlock
{
Text = viewModel.Summary,
FontSize = 14,
Opacity = 0.78,
TextWrapping = TextWrapping.Wrap,
});
panel.Children.Add(new TextBlock
{
Text = $"Owner: {viewModel.Owner}",
FontSize = 13,
Opacity = 0.68,
});
panel.Children.Add(new TextBlock
{
Text = $"Next milestone: {viewModel.NextMilestone}",
FontSize = 13,
Opacity = 0.68,
TextWrapping = TextWrapping.Wrap,
});
panel.Children.Add(new Separator { Margin = new Thickness(0, 4) });
panel.Children.Add(new TextBlock
{
Text = "This page is resolved by SamplePageFactory from a ProjectDetailViewModel. The view model only requests navigation through ISampleNavigationService.",
FontSize = 12,
Opacity = 0.7,
TextWrapping = TextWrapping.Wrap,
});
panel.Children.Add(new Button
{
Content = "Open Activity",
HorizontalAlignment = HorizontalAlignment.Left,
Command = viewModel.OpenActivityCommand,
});
return new ContentPage
{
Header = viewModel.Name,
Background = new SolidColorBrush(Color.FromArgb(18, viewModel.AccentColor.R, viewModel.AccentColor.G, viewModel.AccentColor.B)),
Content = new ScrollViewer { Content = panel },
HorizontalContentAlignment = HorizontalAlignment.Stretch,
VerticalContentAlignment = VerticalAlignment.Stretch,
};
}
private static ContentPage CreateProjectActivityPage(ProjectActivityViewModel viewModel)
{
var panel = new StackPanel
{
Margin = new Thickness(24, 20),
Spacing = 10,
};
panel.Children.Add(new TextBlock
{
Text = "Activity Timeline",
FontSize = 24,
FontWeight = FontWeight.Bold,
});
panel.Children.Add(new TextBlock
{
Text = $"Recent updates for {viewModel.Name}. This page was opened from a command on the detail view model.",
FontSize = 13,
Opacity = 0.74,
TextWrapping = TextWrapping.Wrap,
});
foreach (var item in viewModel.Items)
{
panel.Children.Add(new Border
{
Background = new SolidColorBrush(Color.FromArgb(14, viewModel.AccentColor.R, viewModel.AccentColor.G, viewModel.AccentColor.B)),
BorderBrush = new SolidColorBrush(Color.FromArgb(80, viewModel.AccentColor.R, viewModel.AccentColor.G, viewModel.AccentColor.B)),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(6),
Padding = new Thickness(12, 10),
Child = new TextBlock
{
Text = item,
FontSize = 13,
TextWrapping = TextWrapping.Wrap,
}
});
}
return new ContentPage
{
Header = "Activity",
Content = new ScrollViewer { Content = panel },
HorizontalContentAlignment = HorizontalAlignment.Stretch,
VerticalContentAlignment = VerticalAlignment.Stretch,
};
}
}
}

238
samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmViewModels.cs

@ -0,0 +1,238 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Media;
using MiniMvvm;
namespace ControlCatalog.Pages
{
public sealed class NavigationPageMvvmShellViewModel : ViewModelBase
{
private readonly ISampleNavigationService _navigationService;
private string _currentPageHeader = "Not initialized";
private string _lastAction = "Waiting for first load";
private int _navigationDepth;
private ProjectCardViewModel? _selectedProject;
internal NavigationPageMvvmShellViewModel(ISampleNavigationService navigationService)
{
_navigationService = navigationService;
_navigationService.StateChanged += OnStateChanged;
Workspace = new WorkspaceViewModel(CreateProjects(navigationService));
SelectedProject = Workspace.Projects[0];
OpenSelectedProjectCommand = MiniCommand.CreateFromTask(OpenSelectedProjectAsync);
GoBackCommand = MiniCommand.CreateFromTask(_navigationService.GoBackAsync);
PopToRootCommand = MiniCommand.CreateFromTask(_navigationService.PopToRootAsync);
}
internal WorkspaceViewModel Workspace { get; }
public IReadOnlyList<ProjectCardViewModel> Projects => Workspace.Projects;
public MiniCommand OpenSelectedProjectCommand { get; }
public MiniCommand GoBackCommand { get; }
public MiniCommand PopToRootCommand { get; }
public string CurrentPageHeader
{
get => _currentPageHeader;
set => this.RaiseAndSetIfChanged(ref _currentPageHeader, value);
}
public int NavigationDepth
{
get => _navigationDepth;
set => this.RaiseAndSetIfChanged(ref _navigationDepth, value);
}
public string LastAction
{
get => _lastAction;
set => this.RaiseAndSetIfChanged(ref _lastAction, value);
}
public ProjectCardViewModel? SelectedProject
{
get => _selectedProject;
set => this.RaiseAndSetIfChanged(ref _selectedProject, value);
}
public Task InitializeAsync() => _navigationService.NavigateToAsync(Workspace);
private async Task OpenSelectedProjectAsync()
{
if (SelectedProject == null)
return;
await SelectedProject.OpenCommandAsync();
}
private void OnStateChanged(object? sender, NavigationStateChangedEventArgs e)
{
CurrentPageHeader = e.CurrentPageHeader;
NavigationDepth = e.NavigationDepth;
LastAction = e.LastAction;
}
private static IReadOnlyList<ProjectCardViewModel> CreateProjects(ISampleNavigationService navigationService) =>
new[]
{
new ProjectCardViewModel(
"Release Radar",
"Marta Collins",
"Ready for QA",
"Coordinate the 11.0 release checklist and lock down the final regression window.",
"Freeze build on Friday",
Color.Parse("#0063B1"),
navigationService,
new[]
{
"Release notes draft updated with accessibility fixes.",
"Package validation finished for desktop artifacts.",
"Remaining task, confirm browser smoke test coverage."
}),
new ProjectCardViewModel(
"Support Console",
"Jae Kim",
"Active Sprint",
"Consolidate customer incidents into a triage board and route them to platform owners.",
"Triage review in 2 hours",
Color.Parse("#0F7B0F"),
navigationService,
new[]
{
"Five customer reports grouped under input routing.",
"Hotfix candidate approved for preview branch.",
"Awaiting macOS verification on native embed scenarios."
}),
new ProjectCardViewModel(
"Docs Refresh",
"Anika Patel",
"Needs Review",
"Refresh navigation samples and walkthrough docs so the gallery matches the current API.",
"Sample review tomorrow",
Color.Parse("#8E562E"),
navigationService,
new[]
{
"NavigationPage sample matrix reviewed with design.",
"MVVM walkthrough draft linked from the docs backlog.",
"Outstanding task, capture one more screenshot for drawer navigation."
}),
};
}
internal sealed class WorkspaceViewModel : ViewModelBase
{
public WorkspaceViewModel(IReadOnlyList<ProjectCardViewModel> projects)
{
Projects = projects;
}
public string Title => "Team Workspace";
public string Description =>
"Each card is a project view model with its own command. The command asks ISampleNavigationService to navigate with the next view model, and SamplePageFactory resolves the matching ContentPage.";
public IReadOnlyList<ProjectCardViewModel> Projects { get; }
}
public sealed class ProjectCardViewModel : ViewModelBase
{
private readonly ISampleNavigationService _navigationService;
internal ProjectCardViewModel(
string name,
string owner,
string status,
string summary,
string nextMilestone,
Color accentColor,
ISampleNavigationService navigationService,
IReadOnlyList<string> activityItems)
{
Name = name;
Owner = owner;
Status = status;
Summary = summary;
NextMilestone = nextMilestone;
AccentColor = accentColor;
ActivityItems = activityItems;
_navigationService = navigationService;
OpenCommand = MiniCommand.CreateFromTask(OpenCommandAsync);
}
public string Name { get; }
public string Owner { get; }
public string Status { get; }
public string Summary { get; }
public string NextMilestone { get; }
public Color AccentColor { get; }
public IReadOnlyList<string> ActivityItems { get; }
public MiniCommand OpenCommand { get; }
public Task OpenCommandAsync()
{
return _navigationService.NavigateToAsync(new ProjectDetailViewModel(this, _navigationService));
}
}
internal sealed class ProjectDetailViewModel : ViewModelBase
{
private readonly ProjectCardViewModel _project;
private readonly ISampleNavigationService _navigationService;
public ProjectDetailViewModel(ProjectCardViewModel project, ISampleNavigationService navigationService)
{
_project = project;
_navigationService = navigationService;
OpenActivityCommand = MiniCommand.CreateFromTask(OpenActivityAsync);
}
public string Name => _project.Name;
public string Owner => _project.Owner;
public string Status => _project.Status;
public string Summary => _project.Summary;
public string NextMilestone => _project.NextMilestone;
public Color AccentColor => _project.AccentColor;
public MiniCommand OpenActivityCommand { get; }
private Task OpenActivityAsync()
{
return _navigationService.NavigateToAsync(new ProjectActivityViewModel(_project));
}
}
internal sealed class ProjectActivityViewModel : ViewModelBase
{
public ProjectActivityViewModel(ProjectCardViewModel project)
{
Name = project.Name;
AccentColor = project.AccentColor;
Items = project.ActivityItems;
}
public string Name { get; }
public Color AccentColor { get; }
public IReadOnlyList<string> Items { get; }
}
}

6
samples/ControlCatalog/Pages/NavigationPage/NavigationPagePassDataPage.xaml.cs

@ -19,6 +19,7 @@ namespace ControlCatalog.Pages
new("Emma Brown", "UX Researcher", "Germany", Color.Parse("#F44336")),
};
private bool _initialized;
private bool _isLoaded;
public NavigationPagePassDataPage()
@ -31,6 +32,11 @@ namespace ControlCatalog.Pages
{
_isLoaded = true;
if (_initialized)
return;
_initialized = true;
DemoNav.Pushed += (s, ev) => AppendNavigationLog($"Pushed → {ev.Page?.Header}");
DemoNav.Popped += (s, ev) => AppendNavigationLog($"Popped ← {ev.Page?.Header}");

24
samples/ControlCatalog/Pages/NavigationPage/NavigationPageScrollAwarePage.xaml.cs

@ -21,8 +21,10 @@ namespace ControlCatalog.Pages
}
private IDisposable? _scrollSubscription;
private ScrollViewer? _scrollViewer;
private double _lastScrollY;
private double _currentTranslateY;
private bool _initialized;
public NavigationPageScrollAwarePage()
{
@ -33,12 +35,21 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
var scrollViewer = new ScrollViewer { Content = BuildLongContent() };
if (_initialized)
{
if (_scrollViewer != null)
Dispatcher.UIThread.Post(() => AttachScrollWatcher(_scrollViewer), DispatcherPriority.Loaded);
return;
}
_initialized = true;
_scrollViewer = new ScrollViewer { Content = BuildLongContent() };
var rootPage = new ContentPage
{
Header = "Scroll to Hide Bar",
Content = scrollViewer,
Content = _scrollViewer,
HorizontalContentAlignment = HorizontalAlignment.Stretch,
VerticalContentAlignment = VerticalAlignment.Stretch,
};
@ -46,13 +57,18 @@ namespace ControlCatalog.Pages
NavigationPage.SetBarLayoutBehavior(rootPage, BarLayoutBehavior.Overlay);
await DemoNav.PushAsync(rootPage, null);
Dispatcher.UIThread.Post(() => AttachScrollWatcher(scrollViewer), DispatcherPriority.Loaded);
Dispatcher.UIThread.Post(() => AttachScrollWatcher(_scrollViewer), DispatcherPriority.Loaded);
}
private void OnUnloaded(object? sender, RoutedEventArgs e) => DetachScrollWatcher();
private void AttachScrollWatcher(ScrollViewer sv)
private void AttachScrollWatcher(ScrollViewer? sv)
{
if (sv == null)
return;
DetachScrollWatcher();
var navBar = DemoNav.GetVisualDescendants().OfType<Border>()
.FirstOrDefault(b => b.Name == "PART_NavigationBar");
if (navBar == null)

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageStackPage.xaml.cs

@ -9,6 +9,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageStackPage : UserControl
{
private bool _initialized;
private int _pageCount;
public NavigationPageStackPage()
@ -19,6 +20,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
DemoNav.Pushed += (s, ev) => RefreshStack();
DemoNav.Popped += (s, ev) => RefreshStack();
DemoNav.PoppedToRoot += (s, ev) => RefreshStack();

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageTitlePage.xaml.cs

@ -8,6 +8,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageTitlePage : UserControl
{
private bool _initialized;
private int _pageCount;
public NavigationPageTitlePage()
@ -18,6 +19,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
await DemoNav.PushAsync(NavigationDemoHelper.MakePage("Home", "Choose a header type and tap 'Push'.", 0), null);
StatusText.Text = "Current: Home";
}

5
samples/ControlCatalog/Pages/NavigationPage/NavigationPageToolbarPage.xaml.cs

@ -7,6 +7,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageToolbarPage : UserControl
{
private bool _initialized;
private int _pageCount;
private int _itemCount;
private ContentPage? _rootPage;
@ -20,6 +21,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
_rootPage = NavigationDemoHelper.MakePage("CommandBar Demo",
"Use the panel to add CommandBar items.\nTop items appear inside the navigation bar.\nBottom items appear as a separate bar.", 0);
ApplyPosition();

8
samples/ControlCatalog/Pages/NavigationPage/NavigationPageTransitionsPage.xaml.cs

@ -8,6 +8,7 @@ namespace ControlCatalog.Pages
{
public partial class NavigationPageTransitionsPage : UserControl
{
private bool _initialized;
private int _pageCount;
public NavigationPageTransitionsPage()
@ -18,6 +19,13 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
{
UpdateTransition();
return;
}
_initialized = true;
await DemoNav.PushAsync(NavigationDemoHelper.MakePage("Transitions", "Choose a transition type and push pages.", 0), null);
UpdateTransition();
}

6
samples/ControlCatalog/Pages/NavigationPage/PulseAppPage.xaml.cs

@ -99,7 +99,7 @@ public partial class PulseAppPage : UserControl
Content = homeView,
Background = new SolidColorBrush(BgDashboard),
Header = "Home",
Icon = "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z",
Icon = Geometry.Parse("M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"),
};
var workoutsPage = new ContentPage
@ -107,7 +107,7 @@ public partial class PulseAppPage : UserControl
Content = new PulseWorkoutsView(),
Background = new SolidColorBrush(BgDashboard),
Header = "Workouts",
Icon = "M20.57 14.86L22 13.43 20.57 12 17 15.57 8.43 7 12 3.43 10.57 2 9.14 3.43 7.71 2 5.57 4.14 4.14 2.71 2.71 4.14l1.43 1.43L2 7.71l1.43 1.43L2 10.57 3.43 12 7 8.43 15.57 17 12 20.57 13.43 22l1.43-1.43L16.29 22l2.14-2.14 1.43 1.43 1.43-1.43-1.43-1.43L22 16.29z",
Icon = Geometry.Parse("M20.57 14.86L22 13.43 20.57 12 17 15.57 8.43 7 12 3.43 10.57 2 9.14 3.43 7.71 2 5.57 4.14 4.14 2.71 2.71 4.14l1.43 1.43L2 7.71l1.43 1.43L2 10.57 3.43 12 7 8.43 15.57 17 12 20.57 13.43 22l1.43-1.43L16.29 22l2.14-2.14 1.43 1.43 1.43-1.43-1.43-1.43L22 16.29z"),
};
var profilePage = new ContentPage
@ -115,7 +115,7 @@ public partial class PulseAppPage : UserControl
Content = new PulseProfileView(),
Background = new SolidColorBrush(BgDashboard),
Header = "Profile",
Icon = "M12 2C9.243 2 7 4.243 7 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5zM12 14c-5.523 0-10 3.582-10 8a1 1 0 001 1h18a1 1 0 001-1c0-4.418-4.477-8-10-8z",
Icon = Geometry.Parse("M12 2C9.243 2 7 4.243 7 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5zM12 14c-5.523 0-10 3.582-10 8a1 1 0 001 1h18a1 1 0 001-1c0-4.418-4.477-8-10-8z"),
};
tp.Pages = new ObservableCollection<Page> { homePage, workoutsPage, profilePage };

35
samples/ControlCatalog/Pages/NavigationPage/RetroGamingAppPage.xaml

@ -48,6 +48,7 @@
<UserControl.Styles>
<!-- Nav back button: square bordered box -->
<Style Selector="NavigationPage /template/ Button#PART_BackButton">
<Setter Property="Margin" Value="4,0,0,0"/>
<Setter Property="Width" Value="32"/>
<Setter Property="Height" Value="32"/>
<Setter Property="Padding" Value="0"/>
@ -55,22 +56,43 @@
<Setter Property="BorderBrush" Value="{StaticResource RetroCyan}"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="Background" Value="{StaticResource RetroSurface}"/>
<Setter Property="Foreground" Value="{StaticResource RetroCyan}"/>
</Style>
<Style Selector="NavigationPage /template/ Button#PART_BackButton PathIcon">
<Style Selector="NavigationPage /template/ Button#PART_BackButton:pointerover">
<Setter Property="Background" Value="#3d2b6e"/>
<Setter Property="BorderBrush" Value="{StaticResource RetroCyan}"/>
<Setter Property="Foreground" Value="{StaticResource RetroCyan}"/>
</Style>
<Style Selector="NavigationPage /template/ Button#PART_BackButton:pressed">
<Setter Property="Background" Value="#1e1035"/>
<Setter Property="BorderBrush" Value="{StaticResource RetroCyan}"/>
<Setter Property="Foreground" Value="{StaticResource RetroCyan}"/>
</Style>
<Style Selector="NavigationPage /template/ Path#PART_BackButtonDefaultIcon">
<Setter Property="Fill" Value="{StaticResource RetroCyan}"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
</Style>
<Style Selector="NavigationPage /template/ Button#PART_BackButton:pointerover Path#PART_BackButtonDefaultIcon">
<Setter Property="Fill" Value="{StaticResource RetroCyan}"/>
</Style>
<Style Selector="NavigationPage /template/ Button#PART_BackButton:pressed Path#PART_BackButtonDefaultIcon">
<Setter Property="Fill" Value="{StaticResource RetroCyan}"/>
</Style>
<Style Selector="NavigationPage /template/ Button#PART_BackButton /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#cc2d1b4e"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StaticResource RetroCyan}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="Padding" Value="0"/>
</Style>
<Style Selector="NavigationPage /template/ Button#PART_BackButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#cc3d2b6e"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StaticResource RetroCyan}"/>
</Style>
<Style Selector="NavigationPage /template/ Button#PART_BackButton:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#cc1e1035"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StaticResource RetroCyan}"/>
</Style>
<!-- Primary purple button (INSERT COIN TO PLAY) -->
@ -153,10 +175,5 @@
<Style Selector="Button.retro-fab:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#cccc00"/>
</Style>
<!-- Nav bar back button: left margin so it doesn't hug the edge -->
<Style Selector="NavigationPage /template/ Button#PART_BackButton">
<Setter Property="Margin" Value="4,0,0,0"/>
</Style>
</UserControl.Styles>
</UserControl>

43
samples/ControlCatalog/Pages/NavigationPage/RetroGamingAppPage.xaml.cs

@ -51,11 +51,30 @@ public partial class RetroGamingAppPage : UserControl
_infoPanel.IsVisible = Bounds.Width >= 650;
}
void ApplyHomeNavigationBarAppearance()
{
if (_nav == null)
return;
_nav.Resources["NavigationBarBackground"] = new SolidColorBrush(SurfaceColor);
_nav.Resources["NavigationBarForeground"] = new SolidColorBrush(CyanColor);
}
void ApplyDetailNavigationBarAppearance()
{
if (_nav == null)
return;
_nav.Resources["NavigationBarBackground"] = Brushes.Transparent;
_nav.Resources["NavigationBarForeground"] = new SolidColorBrush(CyanColor);
}
ContentPage BuildHomePage()
{
var page = new ContentPage { Background = new SolidColorBrush(BgColor) };
page.Header = BuildPixelArcadeLogo();
NavigationPage.SetTopCommandBar(page, BuildNavBarRight());
ApplyHomeNavigationBarAppearance();
var panel = new Panel();
panel.Children.Add(BuildHomeTabbedPage());
@ -174,7 +193,7 @@ public partial class RetroGamingAppPage : UserControl
var homeTab = new ContentPage
{
Header = "Home",
Icon = "M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z",
Icon = Geometry.Parse("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z"),
Background = new SolidColorBrush(BgColor),
Content = homeView,
};
@ -185,7 +204,7 @@ public partial class RetroGamingAppPage : UserControl
var gamesTab = new ContentPage
{
Header = "Games",
Icon = "M7.97,16L5,19C4.67,19.3 4.23,19.5 3.75,19.5A1.75,1.75 0 0,1 2,17.75V17.5L3,10.12C3.21,7.81 5.14,6 7.5,6H16.5C18.86,6 20.79,7.81 21,10.12L22,17.5V17.75A1.75,1.75 0 0,1 20.25,19.5C19.77,19.5 19.33,19.3 19,19L16.03,16H7.97M7,9V11H5V13H7V15H9V13H11V11H9V9H7M14.5,12A1.5,1.5 0 0,0 13,13.5A1.5,1.5 0 0,0 14.5,15A1.5,1.5 0 0,0 16,13.5A1.5,1.5 0 0,0 14.5,12M17.5,9A1.5,1.5 0 0,0 16,10.5A1.5,1.5 0 0,0 17.5,12A1.5,1.5 0 0,0 19,10.5A1.5,1.5 0 0,0 17.5,9Z",
Icon = Geometry.Parse("M7.97,16L5,19C4.67,19.3 4.23,19.5 3.75,19.5A1.75,1.75 0 0,1 2,17.75V17.5L3,10.12C3.21,7.81 5.14,6 7.5,6H16.5C18.86,6 20.79,7.81 21,10.12L22,17.5V17.75A1.75,1.75 0 0,1 20.25,19.5C19.77,19.5 19.33,19.3 19,19L16.03,16H7.97M7,9V11H5V13H7V15H9V13H11V11H9V9H7M14.5,12A1.5,1.5 0 0,0 13,13.5A1.5,1.5 0 0,0 14.5,15A1.5,1.5 0 0,0 16,13.5A1.5,1.5 0 0,0 14.5,12M17.5,9A1.5,1.5 0 0,0 16,10.5A1.5,1.5 0 0,0 17.5,12A1.5,1.5 0 0,0 19,10.5A1.5,1.5 0 0,0 17.5,9Z"),
Background = new SolidColorBrush(BgColor),
Content = gamesView,
};
@ -193,7 +212,7 @@ public partial class RetroGamingAppPage : UserControl
var favTab = new ContentPage
{
Header = "Favorites",
Icon = "M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z",
Icon = Geometry.Parse("M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"),
Background = new SolidColorBrush(BgColor),
Content = new RetroGamingFavoritesView(),
};
@ -201,7 +220,7 @@ public partial class RetroGamingAppPage : UserControl
var profileTab = new ContentPage
{
Header = "Profile",
Icon = "M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z",
Icon = Geometry.Parse("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"),
Background = new SolidColorBrush(BgColor),
Content = new RetroGamingProfileView(),
};
@ -260,7 +279,8 @@ public partial class RetroGamingAppPage : UserControl
async void PushDetailPage(string gameTitle)
{
if (_nav == null) return;
if (_nav == null)
return;
var detailView = new RetroGamingDetailView(gameTitle);
@ -271,8 +291,13 @@ public partial class RetroGamingAppPage : UserControl
};
NavigationPage.SetBarLayoutBehavior(page, BarLayoutBehavior.Overlay);
page.NavigatedTo += (_, _) => { if (_nav != null) _nav.Resources["NavigationBarBackground"] = Brushes.Transparent; };
page.NavigatedFrom += (_, _) => { if (_nav != null) _nav.Resources["NavigationBarBackground"] = new SolidColorBrush(SurfaceColor); };
page.Navigating += args =>
{
if (args.NavigationType == NavigationType.Pop)
ApplyHomeNavigationBarAppearance();
return Task.CompletedTask;
};
var cmdBar = new StackPanel
{
@ -301,6 +326,10 @@ public partial class RetroGamingAppPage : UserControl
cmdBar.Children.Add(shareBtn);
NavigationPage.SetTopCommandBar(page, cmdBar);
ApplyDetailNavigationBarAppearance();
await _nav.PushAsync(page);
if (!ReferenceEquals(_nav.CurrentPage, page))
ApplyHomeNavigationBarAppearance();
}
}

7
samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs

@ -31,6 +31,13 @@ namespace ControlCatalog.Pages
("Appearance", "Custom Templates",
"Override pip item templates to create squares, pills, numbers, or any custom shape.",
() => new PipsPagerCustomTemplatesPage()),
("Showcases", "Care Companion",
"A health care onboarding flow using PipsPager as the page indicator for a CarouselPage.",
() => new CareCompanionAppPage()),
("Showcases", "Sanctuary",
"A travel discovery app using PipsPager as the page indicator for a CarouselPage.",
() => new SanctuaryShowcasePage()),
};
public PipsPagerPage()

2
samples/ControlCatalog/Pages/TabbedDemoPage.xaml.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.Pages
"Populate a TabbedPage by adding ContentPage objects directly to the Pages collection.",
() => new TabbedPageCollectionPage()),
("Populate", "Data Templates",
"Populate a TabbedPage with a data collection and a custom PageTemplate to render each item.",
"Bind TabbedPage to an ObservableCollection, add or remove tabs at runtime, and switch the page template.",
() => new TabbedPageDataTemplatePage()),
// Appearance

16
samples/ControlCatalog/Pages/TabbedPage/TabbedPageCustomTabBarPage.xaml.cs

@ -5,7 +5,6 @@ namespace ControlCatalog.Pages
{
public partial class TabbedPageCustomTabBarPage : UserControl
{
// Fluent UI icon geometries (24x24 viewbox)
private static readonly StreamGeometry HomeGeometry =
StreamGeometry.Parse("M12.9942 2.79444C12.4118 2.30208 11.5882 2.30208 11.0058 2.79444L3.50582 9.39444C3.18607 9.66478 3 10.0634 3 10.4828V20.25C3 20.9404 3.55964 21.5 4.25 21.5H8.25C8.94036 21.5 9.5 20.9404 9.5 20.25V14.75C9.5 14.6119 9.61193 14.5 9.75 14.5H14.25C14.3881 14.5 14.5 14.6119 14.5 14.75V20.25C14.5 20.9404 15.0596 21.5 15.75 21.5H19.75C20.4404 21.5 21 20.9404 21 20.25V10.4828C21 10.0634 20.8139 9.66478 20.4942 9.39444L12.9942 2.79444Z");
private static readonly StreamGeometry WalletGeometry =
@ -25,16 +24,11 @@ namespace ControlCatalog.Pages
private void SetupIcons()
{
SetIcon(HomePage, HomeGeometry);
SetIcon(WalletPage, WalletGeometry);
SetIcon(SendPage, SendGeometry);
SetIcon(ActivityPage, ActivityGeometry);
SetIcon(ProfilePage, ProfileGeometry);
}
private static void SetIcon(ContentPage page, StreamGeometry geometry)
{
page.Icon = geometry;
HomePage.Icon = new PathIcon { Data = HomeGeometry };
WalletPage.Icon = new PathIcon { Data = WalletGeometry };
SendPage.Icon = new PathIcon { Data = SendGeometry };
ActivityPage.Icon = new PathIcon { Data = ActivityGeometry };
ProfilePage.Icon = new PathIcon { Data = ProfileGeometry };
}
}
}

11
samples/ControlCatalog/Pages/TabbedPage/TabbedPageCustomizationPage.xaml.cs

@ -109,14 +109,9 @@ namespace ControlCatalog.Pages
private void OnShowIconsChanged(object? sender, RoutedEventArgs e)
{
bool show = ShowIconsCheck.IsChecked == true;
SetIcon(HomePage, show ? HomeGeometry : null);
SetIcon(SearchPage, show ? SearchGeometry : null);
SetIcon(SettingsPage, show ? SettingsGeometry : null);
}
private static void SetIcon(ContentPage page, StreamGeometry? geometry)
{
page.Icon = geometry;
HomePage.Icon = show ? new PathIcon { Data = HomeGeometry } : null;
SearchPage.Icon = show ? new PathIcon { Data = SearchGeometry } : null;
SettingsPage.Icon = show ? new PathIcon { Data = SettingsGeometry } : null;
}
private void OnTabEnabledChanged(object? sender, RoutedEventArgs e)

13
samples/ControlCatalog/Pages/TabbedPage/TabbedPageDataTemplatePage.xaml

@ -8,16 +8,25 @@
Foreground="{DynamicResource SystemControlHighlightAccentBrush}" />
<TextBlock FontSize="12" Opacity="0.7" TextWrapping="Wrap"
Text="Bind a TabbedPage to a data collection and use PageTemplate to render each item as a ContentPage." />
Text="Bind a TabbedPage to a data collection, render each item with PageTemplate, and switch templates at runtime." />
<Separator />
<Button Content="Add Category" Click="OnAddCategory" HorizontalAlignment="Stretch" />
<Button Content="Remove Last" Click="OnRemoveCategory" HorizontalAlignment="Stretch" />
<Button Content="Switch Template" Click="OnSwitchTemplate" HorizontalAlignment="Stretch" />
<Separator />
<TextBlock x:Name="StatusText" Text="3 categories" Opacity="0.7" />
<TextBlock Text="Navigate" FontWeight="SemiBold" FontSize="13" />
<StackPanel Spacing="6">
<Button Content="Previous" Click="OnPrevious" HorizontalAlignment="Stretch" />
<Button Content="Next" Click="OnNext" HorizontalAlignment="Stretch" />
</StackPanel>
<Separator />
<TextBlock x:Name="StatusText" Text="3 tabs" Opacity="0.7" />
</StackPanel>
</ScrollViewer>

157
samples/ControlCatalog/Pages/TabbedPage/TabbedPageDataTemplatePage.xaml.cs

@ -1,5 +1,7 @@
using System.Collections.ObjectModel;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
@ -8,18 +10,32 @@ namespace ControlCatalog.Pages
{
public partial class TabbedPageDataTemplatePage : UserControl
{
private static readonly (string Name, string Color)[] CategoryData =
private sealed class CategoryViewModel
{
("Electronics", "#1565C0"),
("Books", "#2E7D32"),
("Clothing", "#6A1B9A"),
public string Name { get; }
public string Color { get; }
public CategoryViewModel(string name, string color)
{
Name = name;
Color = color;
}
}
private static readonly CategoryViewModel[] InitialData =
{
new("Electronics", "#1565C0"), new("Books", "#2E7D32"), new("Clothing", "#6A1B9A"),
};
private static readonly string[] AddNames = { "Sports", "Music", "Garden", "Toys", "Food" };
private static readonly string[] AddColors = { "#E53935", "#F57C00", "#00796B", "#E91E63", "#3F51B5" };
private static readonly CategoryViewModel[] AddData =
{
new("Sports", "#E53935"), new("Music", "#F57C00"), new("Garden", "#00796B"), new("Toys", "#E91E63"),
new("Food", "#3F51B5")
};
private readonly ObservableCollection<Page> _pages = new();
private readonly ObservableCollection<CategoryViewModel> _items = new();
private int _addCounter;
private bool _useDetailTemplate = true;
private TabbedPage? _tabbedPage;
public TabbedPageDataTemplatePage()
@ -30,48 +46,99 @@ namespace ControlCatalog.Pages
private void OnLoaded(object? sender, RoutedEventArgs e)
{
foreach (var (name, color) in CategoryData)
_pages.Add(CreatePage(name, color));
if (_tabbedPage != null)
return;
_addCounter = CategoryData.Length;
foreach (var vm in InitialData)
_items.Add(vm);
_addCounter = InitialData.Length;
_useDetailTemplate = true;
_tabbedPage = new TabbedPage
{
TabPlacement = TabPlacement.Top,
Pages = _pages
TabPlacement = TabPlacement.Top, ItemsSource = _items, PageTemplate = CreatePageTemplate()
};
_tabbedPage.SelectionChanged += OnSelectionChanged;
TabbedPageHost.Children.Add(_tabbedPage);
UpdateStatus();
}
private void OnAddCategory(object? sender, RoutedEventArgs e)
{
var idx = _addCounter % AddNames.Length;
var name = AddNames[idx] + (_addCounter >= AddNames.Length ? $" {_addCounter / AddNames.Length + 1}" : "");
_pages.Add(CreatePage(name, AddColors[idx]));
var idx = _addCounter % AddData.Length;
var vm = AddData[idx];
var suffix = _addCounter >= AddData.Length ? $" {_addCounter / AddData.Length + 1}" : "";
_items.Add(new CategoryViewModel(vm.Name + suffix, vm.Color));
_addCounter++;
UpdateStatus();
}
private void OnRemoveCategory(object? sender, RoutedEventArgs e)
{
if (_pages.Count > 0)
if (_items.Count > 0)
{
_pages.RemoveAt(_pages.Count - 1);
_items.RemoveAt(_items.Count - 1);
UpdateStatus();
}
}
private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e) => UpdateStatus();
private void OnSwitchTemplate(object? sender, RoutedEventArgs e)
{
if (_tabbedPage == null)
return;
_useDetailTemplate = !_useDetailTemplate;
_tabbedPage.PageTemplate = CreatePageTemplate();
UpdateStatus();
}
private void OnPrevious(object? sender, RoutedEventArgs e)
{
if (_tabbedPage == null)
return;
if (_tabbedPage.SelectedIndex > 0)
_tabbedPage.SelectedIndex--;
}
private void OnNext(object? sender, RoutedEventArgs e)
{
if (_tabbedPage == null)
return;
if (_tabbedPage.SelectedIndex < _items.Count - 1)
_tabbedPage.SelectedIndex++;
}
private void UpdateStatus()
{
StatusText.Text = $"{_pages.Count} categor{(_pages.Count == 1 ? "y" : "ies")}";
var count = _items.Count;
var index = _tabbedPage?.SelectedIndex ?? -1;
StatusText.Text = count == 0 ? "No tabs" : $"Tab {index + 1} of {count} (index {index})";
}
private static ContentPage CreatePage(string name, string color) => new()
private IDataTemplate CreatePageTemplate()
{
Header = name,
Content = new StackPanel
return new FuncDataTemplate<CategoryViewModel>((vm, _) => CreatePage(vm, _useDetailTemplate));
}
private static ContentPage CreatePage(CategoryViewModel? vm, bool useDetailTemplate)
{
if (vm is null)
return new ContentPage();
return new ContentPage
{
Header = vm.Name, Content = useDetailTemplate ? CreateDetailContent(vm) : CreateShowcaseContent(vm)
};
}
private static Control CreateDetailContent(CategoryViewModel vm)
{
return new StackPanel
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
@ -80,15 +147,15 @@ namespace ControlCatalog.Pages
{
new TextBlock
{
Text = name,
Text = vm.Name,
FontSize = 24,
FontWeight = FontWeight.SemiBold,
Foreground = new SolidColorBrush(Color.Parse(color)),
Foreground = new SolidColorBrush(Color.Parse(vm.Color)),
HorizontalAlignment = HorizontalAlignment.Center
},
new TextBlock
{
Text = $"Tab for category: {name}",
Text = $"Tab for category: {vm.Name}",
FontSize = 13,
Opacity = 0.7,
TextWrapping = TextWrapping.Wrap,
@ -96,7 +163,45 @@ namespace ControlCatalog.Pages
MaxWidth = 280
}
}
}
};
};
}
private static Control CreateShowcaseContent(CategoryViewModel vm)
{
var accent = Color.Parse(vm.Color);
return new Border
{
Margin = new Thickness(24),
CornerRadius = new CornerRadius(18),
Background = new SolidColorBrush(accent),
Padding = new Thickness(28),
Child = new StackPanel
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Spacing = 10,
Children =
{
new TextBlock
{
Text = vm.Name,
FontSize = 28,
FontWeight = FontWeight.Bold,
Foreground = Brushes.White,
HorizontalAlignment = HorizontalAlignment.Center
},
new TextBlock
{
Text = "Template switched at runtime",
FontSize = 14,
Foreground = Brushes.White,
Opacity = 0.9,
HorizontalAlignment = HorizontalAlignment.Center
}
}
}
};
}
}
}

8
samples/ControlCatalog/Pages/TabbedPage/TabbedPageFabPage.xaml.cs

@ -28,10 +28,10 @@ namespace ControlCatalog.Pages
private void SetupIcons()
{
FeedPage.Icon = FeedGeometry;
DiscoverPage.Icon = DiscoverGeometry;
AlertsPage.Icon = AlertsGeometry;
ProfilePage.Icon = ProfileGeometry;
FeedPage.Icon = new PathIcon { Data = FeedGeometry };
DiscoverPage.Icon = new PathIcon { Data = DiscoverGeometry };
AlertsPage.Icon = new PathIcon { Data = AlertsGeometry };
ProfilePage.Icon = new PathIcon { Data = ProfileGeometry };
}
private void OnFabClicked(object? sender, RoutedEventArgs e)

13
samples/ControlCatalog/Pages/TabbedPage/TabbedPageGesturePage.xaml.cs

@ -1,4 +1,6 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input.GestureRecognizers;
namespace ControlCatalog.Pages
{
@ -7,6 +9,7 @@ namespace ControlCatalog.Pages
public TabbedPageGesturePage()
{
InitializeComponent();
EnableMouseSwipeGesture(DemoTabs);
}
private void OnGestureEnabledChanged(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
@ -26,5 +29,15 @@ namespace ControlCatalog.Pages
_ => TabPlacement.Top
};
}
private static void EnableMouseSwipeGesture(Control control)
{
var recognizer = control.GestureRecognizers
.OfType<SwipeGestureRecognizer>()
.FirstOrDefault();
if (recognizer is not null)
recognizer.IsMouseEnabled = true;
}
}
}

36
samples/ControlCatalog/Pages/TabbedPage/TabbedPageWithNavigationPage.xaml

@ -29,27 +29,21 @@
CornerRadius="6"
ClipToBounds="True">
<TabbedPage x:Name="DemoTabs" TabPlacement="Bottom">
<ContentPage Header="Browse">
<NavigationPage x:Name="BrowseNav">
<NavigationPage.Resources>
<SolidColorBrush x:Key="NavigationBarBackground" Color="Transparent" />
</NavigationPage.Resources>
</NavigationPage>
</ContentPage>
<ContentPage Header="Search">
<NavigationPage x:Name="SearchNav">
<NavigationPage.Resources>
<SolidColorBrush x:Key="NavigationBarBackground" Color="Transparent" />
</NavigationPage.Resources>
</NavigationPage>
</ContentPage>
<ContentPage Header="Account">
<NavigationPage x:Name="AccountNav">
<NavigationPage.Resources>
<SolidColorBrush x:Key="NavigationBarBackground" Color="Transparent" />
</NavigationPage.Resources>
</NavigationPage>
</ContentPage>
<NavigationPage x:Name="BrowseNav" Header="Browse">
<NavigationPage.Resources>
<SolidColorBrush x:Key="NavigationBarBackground" Color="Transparent" />
</NavigationPage.Resources>
</NavigationPage>
<NavigationPage x:Name="SearchNav" Header="Search">
<NavigationPage.Resources>
<SolidColorBrush x:Key="NavigationBarBackground" Color="Transparent" />
</NavigationPage.Resources>
</NavigationPage>
<NavigationPage x:Name="AccountNav" Header="Account">
<NavigationPage.Resources>
<SolidColorBrush x:Key="NavigationBarBackground" Color="Transparent" />
</NavigationPage.Resources>
</NavigationPage>
</TabbedPage>
</Border>
</DockPanel>

6
samples/ControlCatalog/Pages/TabbedPage/TabbedPageWithNavigationPage.xaml.cs

@ -7,6 +7,8 @@ namespace ControlCatalog.Pages
{
public partial class TabbedPageWithNavigationPage : UserControl
{
private bool _initialized;
public TabbedPageWithNavigationPage()
{
InitializeComponent();
@ -15,6 +17,10 @@ namespace ControlCatalog.Pages
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
if (_initialized)
return;
_initialized = true;
await BrowseNav.PushAsync(CreateListPage("Browse", "Items", BrowseNav), null);
await SearchNav.PushAsync(CreateListPage("Search", "Results", SearchNav), null);
await AccountNav.PushAsync(CreateListPage("Account", "Options", AccountNav), null);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save