diff --git a/samples/ControlCatalog/Assets/CurvedHeader/avatar.jpg b/samples/ControlCatalog/Assets/CurvedHeader/avatar.jpg
new file mode 100644
index 0000000000..b7b9610f95
Binary files /dev/null and b/samples/ControlCatalog/Assets/CurvedHeader/avatar.jpg differ
diff --git a/samples/ControlCatalog/Assets/CurvedHeader/featured.jpg b/samples/ControlCatalog/Assets/CurvedHeader/featured.jpg
new file mode 100644
index 0000000000..2bb09f1185
Binary files /dev/null and b/samples/ControlCatalog/Assets/CurvedHeader/featured.jpg differ
diff --git a/samples/ControlCatalog/Assets/CurvedHeader/product1.jpg b/samples/ControlCatalog/Assets/CurvedHeader/product1.jpg
new file mode 100644
index 0000000000..4447a8a6f2
Binary files /dev/null and b/samples/ControlCatalog/Assets/CurvedHeader/product1.jpg differ
diff --git a/samples/ControlCatalog/Assets/CurvedHeader/product2.jpg b/samples/ControlCatalog/Assets/CurvedHeader/product2.jpg
new file mode 100644
index 0000000000..58acb3ebf0
Binary files /dev/null and b/samples/ControlCatalog/Assets/CurvedHeader/product2.jpg differ
diff --git a/samples/ControlCatalog/Assets/CurvedHeader/product3.jpg b/samples/ControlCatalog/Assets/CurvedHeader/product3.jpg
new file mode 100644
index 0000000000..4722989113
Binary files /dev/null and b/samples/ControlCatalog/Assets/CurvedHeader/product3.jpg differ
diff --git a/samples/ControlCatalog/Assets/CurvedHeader/update1.jpg b/samples/ControlCatalog/Assets/CurvedHeader/update1.jpg
new file mode 100644
index 0000000000..d434d6194b
Binary files /dev/null and b/samples/ControlCatalog/Assets/CurvedHeader/update1.jpg differ
diff --git a/samples/ControlCatalog/Assets/CurvedHeader/update2.jpg b/samples/ControlCatalog/Assets/CurvedHeader/update2.jpg
new file mode 100644
index 0000000000..db35f09e02
Binary files /dev/null and b/samples/ControlCatalog/Assets/CurvedHeader/update2.jpg differ
diff --git a/samples/ControlCatalog/Assets/CurvedHeader/update3.jpg b/samples/ControlCatalog/Assets/CurvedHeader/update3.jpg
new file mode 100644
index 0000000000..0859309904
Binary files /dev/null and b/samples/ControlCatalog/Assets/CurvedHeader/update3.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/avatar.jpg b/samples/ControlCatalog/Assets/ModernApp/avatar.jpg
new file mode 100644
index 0000000000..3c55a5af75
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/avatar.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/dest_alps.jpg b/samples/ControlCatalog/Assets/ModernApp/dest_alps.jpg
new file mode 100644
index 0000000000..ebbac8920b
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/dest_alps.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/dest_forest.jpg b/samples/ControlCatalog/Assets/ModernApp/dest_forest.jpg
new file mode 100644
index 0000000000..876be96909
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/dest_forest.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/dest_norway.jpg b/samples/ControlCatalog/Assets/ModernApp/dest_norway.jpg
new file mode 100644
index 0000000000..aec9e2ef36
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/dest_norway.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/exp_angkor.jpg b/samples/ControlCatalog/Assets/ModernApp/exp_angkor.jpg
new file mode 100644
index 0000000000..812859a392
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/exp_angkor.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/exp_tokyo.jpg b/samples/ControlCatalog/Assets/ModernApp/exp_tokyo.jpg
new file mode 100644
index 0000000000..77f73a4af1
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/exp_tokyo.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/gallery_alpine.jpg b/samples/ControlCatalog/Assets/ModernApp/gallery_alpine.jpg
new file mode 100644
index 0000000000..d8b2fce6d8
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/gallery_alpine.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/gallery_bay.jpg b/samples/ControlCatalog/Assets/ModernApp/gallery_bay.jpg
new file mode 100644
index 0000000000..605f072dfe
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/gallery_bay.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/gallery_city.jpg b/samples/ControlCatalog/Assets/ModernApp/gallery_city.jpg
new file mode 100644
index 0000000000..788d63cce9
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/gallery_city.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/gallery_paris.jpg b/samples/ControlCatalog/Assets/ModernApp/gallery_paris.jpg
new file mode 100644
index 0000000000..7e04fcdeca
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/gallery_paris.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/gallery_tropical.jpg b/samples/ControlCatalog/Assets/ModernApp/gallery_tropical.jpg
new file mode 100644
index 0000000000..a27ef779a5
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/gallery_tropical.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/gallery_venice.jpg b/samples/ControlCatalog/Assets/ModernApp/gallery_venice.jpg
new file mode 100644
index 0000000000..fcc67fa623
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/gallery_venice.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/story1.jpg b/samples/ControlCatalog/Assets/ModernApp/story1.jpg
new file mode 100644
index 0000000000..b75847fb11
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/story1.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/story2.jpg b/samples/ControlCatalog/Assets/ModernApp/story2.jpg
new file mode 100644
index 0000000000..664c7ef7ef
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/story2.jpg differ
diff --git a/samples/ControlCatalog/Assets/ModernApp/story3.jpg b/samples/ControlCatalog/Assets/ModernApp/story3.jpg
new file mode 100644
index 0000000000..b949cf63c3
Binary files /dev/null and b/samples/ControlCatalog/Assets/ModernApp/story3.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/cast1.jpg b/samples/ControlCatalog/Assets/Movies/cast1.jpg
new file mode 100644
index 0000000000..2eb838400e
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/cast1.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/cast2.jpg b/samples/ControlCatalog/Assets/Movies/cast2.jpg
new file mode 100644
index 0000000000..42607923c8
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/cast2.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/continue1.jpg b/samples/ControlCatalog/Assets/Movies/continue1.jpg
new file mode 100644
index 0000000000..27457bfe79
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/continue1.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/hero.jpg b/samples/ControlCatalog/Assets/Movies/hero.jpg
new file mode 100644
index 0000000000..f48e206e2e
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/hero.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/morelike1.jpg b/samples/ControlCatalog/Assets/Movies/morelike1.jpg
new file mode 100644
index 0000000000..62852e0d8a
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/morelike1.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/search1.jpg b/samples/ControlCatalog/Assets/Movies/search1.jpg
new file mode 100644
index 0000000000..17cb2fe685
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/search1.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/toprated1.jpg b/samples/ControlCatalog/Assets/Movies/toprated1.jpg
new file mode 100644
index 0000000000..f5de43613e
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/toprated1.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/toprated2.jpg b/samples/ControlCatalog/Assets/Movies/toprated2.jpg
new file mode 100644
index 0000000000..c54cbd5b34
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/toprated2.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/toprated3.jpg b/samples/ControlCatalog/Assets/Movies/toprated3.jpg
new file mode 100644
index 0000000000..c78f4d3278
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/toprated3.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/toprated4.jpg b/samples/ControlCatalog/Assets/Movies/toprated4.jpg
new file mode 100644
index 0000000000..f70cd0d283
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/toprated4.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/trending1.jpg b/samples/ControlCatalog/Assets/Movies/trending1.jpg
new file mode 100644
index 0000000000..b208d69e33
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/trending1.jpg differ
diff --git a/samples/ControlCatalog/Assets/Movies/trending2.jpg b/samples/ControlCatalog/Assets/Movies/trending2.jpg
new file mode 100644
index 0000000000..44fcce2e1b
Binary files /dev/null and b/samples/ControlCatalog/Assets/Movies/trending2.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/cat_hiit.jpg b/samples/ControlCatalog/Assets/Pulse/cat_hiit.jpg
new file mode 100644
index 0000000000..010d1c5162
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/cat_hiit.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/cat_strength.jpg b/samples/ControlCatalog/Assets/Pulse/cat_strength.jpg
new file mode 100644
index 0000000000..bd63302eee
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/cat_strength.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/cat_yoga.jpg b/samples/ControlCatalog/Assets/Pulse/cat_yoga.jpg
new file mode 100644
index 0000000000..64db513b0b
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/cat_yoga.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/ex_bench.jpg b/samples/ControlCatalog/Assets/Pulse/ex_bench.jpg
new file mode 100644
index 0000000000..a188abfa58
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/ex_bench.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/ex_deadlifts.jpg b/samples/ControlCatalog/Assets/Pulse/ex_deadlifts.jpg
new file mode 100644
index 0000000000..b6a272f9f5
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/ex_deadlifts.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/ex_overhead.jpg b/samples/ControlCatalog/Assets/Pulse/ex_overhead.jpg
new file mode 100644
index 0000000000..c516f0d2a8
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/ex_overhead.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/ex_pullups.jpg b/samples/ControlCatalog/Assets/Pulse/ex_pullups.jpg
new file mode 100644
index 0000000000..9086115000
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/ex_pullups.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/ex_squats.jpg b/samples/ControlCatalog/Assets/Pulse/ex_squats.jpg
new file mode 100644
index 0000000000..449be4bcb2
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/ex_squats.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/profile_avatar.jpg b/samples/ControlCatalog/Assets/Pulse/profile_avatar.jpg
new file mode 100644
index 0000000000..0b6b907926
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/profile_avatar.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/rec_fullbody.jpg b/samples/ControlCatalog/Assets/Pulse/rec_fullbody.jpg
new file mode 100644
index 0000000000..7d79ec36b0
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/rec_fullbody.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/rec_mobility.jpg b/samples/ControlCatalog/Assets/Pulse/rec_mobility.jpg
new file mode 100644
index 0000000000..be573b35a7
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/rec_mobility.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/rec_powercore.jpg b/samples/ControlCatalog/Assets/Pulse/rec_powercore.jpg
new file mode 100644
index 0000000000..a72c8e9159
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/rec_powercore.jpg differ
diff --git a/samples/ControlCatalog/Assets/Pulse/workout_hero.jpg b/samples/ControlCatalog/Assets/Pulse/workout_hero.jpg
new file mode 100644
index 0000000000..d2fb1dfe91
Binary files /dev/null and b/samples/ControlCatalog/Assets/Pulse/workout_hero.jpg differ
diff --git a/samples/ControlCatalog/Assets/Restaurant/dish1.jpg b/samples/ControlCatalog/Assets/Restaurant/dish1.jpg
new file mode 100644
index 0000000000..fa47be4b8a
Binary files /dev/null and b/samples/ControlCatalog/Assets/Restaurant/dish1.jpg differ
diff --git a/samples/ControlCatalog/Assets/Restaurant/dish2.jpg b/samples/ControlCatalog/Assets/Restaurant/dish2.jpg
new file mode 100644
index 0000000000..bdcbfb656f
Binary files /dev/null and b/samples/ControlCatalog/Assets/Restaurant/dish2.jpg differ
diff --git a/samples/ControlCatalog/Assets/Restaurant/dish3.jpg b/samples/ControlCatalog/Assets/Restaurant/dish3.jpg
new file mode 100644
index 0000000000..9f4f906f01
Binary files /dev/null and b/samples/ControlCatalog/Assets/Restaurant/dish3.jpg differ
diff --git a/samples/ControlCatalog/Assets/Restaurant/dish4.jpg b/samples/ControlCatalog/Assets/Restaurant/dish4.jpg
new file mode 100644
index 0000000000..2bbaf1db9d
Binary files /dev/null and b/samples/ControlCatalog/Assets/Restaurant/dish4.jpg differ
diff --git a/samples/ControlCatalog/Assets/Restaurant/featured_dish.jpg b/samples/ControlCatalog/Assets/Restaurant/featured_dish.jpg
new file mode 100644
index 0000000000..ad21a354dd
Binary files /dev/null and b/samples/ControlCatalog/Assets/Restaurant/featured_dish.jpg differ
diff --git a/samples/ControlCatalog/Assets/Restaurant/user_avatar.jpg b/samples/ControlCatalog/Assets/Restaurant/user_avatar.jpg
new file mode 100644
index 0000000000..664c7ef7ef
Binary files /dev/null and b/samples/ControlCatalog/Assets/Restaurant/user_avatar.jpg differ
diff --git a/samples/ControlCatalog/Assets/RetroGaming/cyber_city.jpg b/samples/ControlCatalog/Assets/RetroGaming/cyber_city.jpg
new file mode 100644
index 0000000000..e262425fff
Binary files /dev/null and b/samples/ControlCatalog/Assets/RetroGaming/cyber_city.jpg differ
diff --git a/samples/ControlCatalog/Assets/RetroGaming/dungeon_bit.jpg b/samples/ControlCatalog/Assets/RetroGaming/dungeon_bit.jpg
new file mode 100644
index 0000000000..6b8967fc83
Binary files /dev/null and b/samples/ControlCatalog/Assets/RetroGaming/dungeon_bit.jpg differ
diff --git a/samples/ControlCatalog/Assets/RetroGaming/forest_spirit.jpg b/samples/ControlCatalog/Assets/RetroGaming/forest_spirit.jpg
new file mode 100644
index 0000000000..2192a3a72c
Binary files /dev/null and b/samples/ControlCatalog/Assets/RetroGaming/forest_spirit.jpg differ
diff --git a/samples/ControlCatalog/Assets/RetroGaming/hero.jpg b/samples/ControlCatalog/Assets/RetroGaming/hero.jpg
new file mode 100644
index 0000000000..6a265a5ef7
Binary files /dev/null and b/samples/ControlCatalog/Assets/RetroGaming/hero.jpg differ
diff --git a/samples/ControlCatalog/Assets/RetroGaming/neon_ninja.jpg b/samples/ControlCatalog/Assets/RetroGaming/neon_ninja.jpg
new file mode 100644
index 0000000000..6a5994ec45
Binary files /dev/null and b/samples/ControlCatalog/Assets/RetroGaming/neon_ninja.jpg differ
diff --git a/samples/ControlCatalog/Assets/RetroGaming/neon_racer.jpg b/samples/ControlCatalog/Assets/RetroGaming/neon_racer.jpg
new file mode 100644
index 0000000000..68c973ba93
Binary files /dev/null and b/samples/ControlCatalog/Assets/RetroGaming/neon_racer.jpg differ
diff --git a/samples/ControlCatalog/Assets/RetroGaming/pixel_quest.jpg b/samples/ControlCatalog/Assets/RetroGaming/pixel_quest.jpg
new file mode 100644
index 0000000000..9af86cdbb3
Binary files /dev/null and b/samples/ControlCatalog/Assets/RetroGaming/pixel_quest.jpg differ
diff --git a/samples/ControlCatalog/Assets/RetroGaming/space_voids.jpg b/samples/ControlCatalog/Assets/RetroGaming/space_voids.jpg
new file mode 100644
index 0000000000..b39a00967f
Binary files /dev/null and b/samples/ControlCatalog/Assets/RetroGaming/space_voids.jpg differ
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index c71e7a93ad..8304e3e002 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -13,6 +13,12 @@
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index cce09c03f7..c8c496b50c 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -49,9 +49,15 @@
+
+
+
+
@@ -67,6 +73,11 @@
+
+
+
@@ -99,6 +110,11 @@
+
+
+
@@ -128,6 +144,11 @@
+
+
+
@@ -167,6 +188,11 @@
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml
new file mode 100644
index 0000000000..1ea3349129
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml
@@ -0,0 +1,120 @@
+
+
+ M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z
+ 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
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Customize the CommandBar appearance using Background, Foreground, BorderBrush, and CornerRadius.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml.cs
new file mode 100644
index 0000000000..52c98757f0
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml.cs
@@ -0,0 +1,90 @@
+using Avalonia.Controls;
+using Avalonia.Media;
+
+namespace ControlCatalog.Pages
+{
+ public partial class CommandBarCustomizationPage : UserControl
+ {
+ public CommandBarCustomizationPage()
+ {
+ InitializeComponent();
+ }
+
+ private void OnBgPresetChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (LiveBar == null)
+ return;
+
+ switch (BgPresetCombo.SelectedIndex)
+ {
+ case 1:
+ LiveBar.Background = new SolidColorBrush(Color.Parse("#0078D4"));
+ break;
+ case 2:
+ LiveBar.Background = new SolidColorBrush(Color.Parse("#1C1C1E"));
+ break;
+ case 3:
+ LiveBar.Background = new LinearGradientBrush
+ {
+ StartPoint = new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Relative),
+ EndPoint = new Avalonia.RelativePoint(1, 0, Avalonia.RelativeUnit.Relative),
+ GradientStops =
+ {
+ new GradientStop(Color.Parse("#3F51B5"), 0),
+ new GradientStop(Color.Parse("#E91E63"), 1)
+ }
+ };
+ break;
+ case 4:
+ LiveBar.Background = Brushes.Transparent;
+ break;
+ default:
+ LiveBar.ClearValue(BackgroundProperty);
+ break;
+ }
+ }
+
+ private void OnFgChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (LiveBar == null)
+ return;
+
+ switch (FgCombo.SelectedIndex)
+ {
+ case 1:
+ LiveBar.Foreground = Brushes.White;
+ break;
+ case 2:
+ LiveBar.Foreground = Brushes.Black;
+ break;
+ default:
+ LiveBar.ClearValue(ForegroundProperty);
+ break;
+ }
+ }
+
+ private void OnRadiusChanged(object? sender, Avalonia.Controls.Primitives.RangeBaseValueChangedEventArgs e)
+ {
+ if (LiveBar == null)
+ return;
+
+ var r = (int)RadiusSlider.Value;
+ LiveBar.CornerRadius = new Avalonia.CornerRadius(r);
+ RadiusLabel.Text = $"{r}";
+ }
+
+ private void OnBorderChanged(object? sender, Avalonia.Controls.Primitives.RangeBaseValueChangedEventArgs e)
+ {
+ if (LiveBar == null)
+ return;
+
+ var t = (int)BorderSlider.Value;
+ LiveBar.BorderThickness = new Avalonia.Thickness(t);
+ BorderLabel.Text = $"{t}";
+ if (t > 0)
+ LiveBar.BorderBrush = Brushes.Gray;
+ else
+ LiveBar.BorderBrush = null;
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml
new file mode 100644
index 0000000000..2f771ab42e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml
@@ -0,0 +1,75 @@
+
+
+ M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z
+ 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
+ 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
+ M15.6,10.79C17.04,10.07 18,8.64 18,7C18,4.79 16.21,3 14,3H7V21H14.73C16.78,21 18.5,19.37 18.5,17.32C18.5,15.82 17.72,14.53 16.5,13.77C16.2,13.59 15.9,13.44 15.6,13.32V10.79M10,6.5H13C13.83,6.5 14.5,7.17 14.5,8C14.5,8.83 13.83,9.5 13,9.5H10V6.5M13.5,17.5H10V14H13.5C14.33,14 15,14.67 15,15.5C15,16.33 14.33,17.5 13.5,17.5Z
+ M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z
+ M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs
new file mode 100644
index 0000000000..45a1c95527
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs
@@ -0,0 +1,45 @@
+using System.Collections.Specialized;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace ControlCatalog.Pages
+{
+ public partial class CommandBarDynamicOverflowPage : UserControl
+ {
+ public CommandBarDynamicOverflowPage()
+ {
+ InitializeComponent();
+ ((INotifyCollectionChanged)DemoBar.OverflowItems).CollectionChanged += OnOverflowChanged;
+ UpdateStatus();
+ }
+
+ private void OnWidthChanged(object? sender, Avalonia.Controls.Primitives.RangeBaseValueChangedEventArgs e)
+ {
+ if (BarContainer == null)
+ return;
+ var width = (int)WidthSlider.Value;
+ BarContainer.Width = width;
+ WidthLabel.Text = $"{width}";
+ }
+
+ private void OnDynamicOverflowChanged(object? sender, RoutedEventArgs e)
+ {
+ if (DemoBar == null)
+ return;
+ DemoBar.IsDynamicOverflowEnabled = DynamicOverflowCheck.IsChecked == true;
+ }
+
+ private void OnOverflowChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ UpdateStatus();
+ }
+
+ private void UpdateStatus()
+ {
+ var total = DemoBar.PrimaryCommands.Count;
+ var overflow = DemoBar.OverflowItems.Count;
+ var visible = total - overflow;
+ StatusText.Text = $"Showing {visible} of {total} commands, {overflow} in overflow";
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml
new file mode 100644
index 0000000000..b83d1c3e57
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml
@@ -0,0 +1,94 @@
+
+
+ M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z
+ 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
+ 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
+ M15.6,10.79C17.04,10.07 18,8.64 18,7C18,4.79 16.21,3 14,3H7V21H14.73C16.78,21 18.5,19.37 18.5,17.32C18.5,15.82 17.72,14.53 16.5,13.77C16.2,13.59 15.9,13.44 15.6,13.32V10.79M10,6.5H13C13.83,6.5 14.5,7.17 14.5,8C14.5,8.83 13.83,9.5 13,9.5H10V6.5M13.5,17.5H10V14H13.5C14.33,14 15,14.67 15,15.5C15,16.33 14.33,17.5 13.5,17.5Z
+ M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z
+ 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
+ M18,3H6V7H18M19,12A1,1 0 0,1 18,11A1,1 0 0,1 19,10A1,1 0 0,1 20,11A1,1 0 0,1 19,12M16,19H8V14H16M19,8H5A3,3 0 0,0 2,11V17H6V21H18V17H22V11A3,3 0 0,0 19,8Z
+ M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A toolbar supporting primary and secondary commands with an optional overflow menu.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml.cs
new file mode 100644
index 0000000000..c624ffcad6
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml.cs
@@ -0,0 +1,27 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace ControlCatalog.Pages
+{
+ public partial class CommandBarFirstLookPage : UserControl
+ {
+ public CommandBarFirstLookPage()
+ {
+ InitializeComponent();
+ }
+
+ private void OnButtonClick(object? sender, RoutedEventArgs e)
+ {
+ if (sender is AppBarButton btn)
+ StatusText.Text = $"{btn.Label} clicked";
+ }
+
+ private void OnToggleChanged(object? sender, RoutedEventArgs e)
+ {
+ if (sender is AppBarToggleButton btn)
+ StatusText.Text = btn.IsChecked == true
+ ? $"{btn.Label} enabled"
+ : $"{btn.Label} disabled";
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml
new file mode 100644
index 0000000000..a1dbea2db1
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml
@@ -0,0 +1,47 @@
+
+
+ M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z
+ 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
+ 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
+ M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z
+
+
+
+
+ The DefaultLabelPosition property controls how button labels are displayed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml.cs
new file mode 100644
index 0000000000..db9cfa3e82
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml.cs
@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+
+namespace ControlCatalog.Pages
+{
+ public partial class CommandBarLabelPositionPage : UserControl
+ {
+ public CommandBarLabelPositionPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml
new file mode 100644
index 0000000000..be3371a8e8
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml
@@ -0,0 +1,74 @@
+
+
+ M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z
+ 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
+ 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
+ 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
+ M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The overflow (ยทยทยท) button reveals secondary commands. Configure its visibility and behavior.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml.cs
new file mode 100644
index 0000000000..a0d5f24b7f
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml.cs
@@ -0,0 +1,54 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace ControlCatalog.Pages
+{
+ public partial class CommandBarOverflowPage : UserControl
+ {
+ private int _primaryCount;
+ private int _secondaryCount;
+
+ public CommandBarOverflowPage()
+ {
+ InitializeComponent();
+ }
+
+ private void OnOverflowVisChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (DemoBar == null)
+ return;
+ DemoBar.OverflowButtonVisibility = OverflowVisCombo.SelectedIndex switch
+ {
+ 1 => CommandBarOverflowButtonVisibility.Visible,
+ 2 => CommandBarOverflowButtonVisibility.Collapsed,
+ _ => CommandBarOverflowButtonVisibility.Auto
+ };
+ }
+
+ private void OnIsOpenChanged(object? sender, RoutedEventArgs e)
+ {
+ if (DemoBar == null)
+ return;
+ DemoBar.IsOpen = IsOpenCheck.IsChecked == true;
+ }
+
+ private void OnIsStickyChanged(object? sender, RoutedEventArgs e)
+ {
+ if (DemoBar == null)
+ return;
+ DemoBar.IsSticky = IsStickyCheck.IsChecked == true;
+ }
+
+ private void OnAddPrimary(object? sender, RoutedEventArgs e)
+ {
+ _primaryCount++;
+ DemoBar.PrimaryCommands.Add(new AppBarButton { Label = $"Cmd {_primaryCount}" });
+ }
+
+ private void OnAddSecondary(object? sender, RoutedEventArgs e)
+ {
+ _secondaryCount++;
+ DemoBar.SecondaryCommands.Add(new AppBarButton { Label = $"Sec {_secondaryCount}" });
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml
new file mode 100644
index 0000000000..d2513038c0
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml
@@ -0,0 +1,77 @@
+
+
+ M15.6,10.79C17.04,10.07 18,8.64 18,7C18,4.79 16.21,3 14,3H7V21H14.73C16.78,21 18.5,19.37 18.5,17.32C18.5,15.82 17.72,14.53 16.5,13.77C16.2,13.59 15.9,13.44 15.6,13.32V10.79M10,6.5H13C13.83,6.5 14.5,7.17 14.5,8C14.5,8.83 13.83,9.5 13,9.5H10V6.5M13.5,17.5H10V14H13.5C14.33,14 15,14.67 15,15.5C15,16.33 14.33,17.5 13.5,17.5Z
+ M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z
+ M5,21H19V19H5V21M12,17A6,6 0 0,0 18,11V3H15.5V11A3.5,3.5 0 0,1 12,14.5A3.5,3.5 0 0,1 8.5,11V3H6V11A6,6 0 0,0 12,17Z
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AppBarToggleButton supports on/off states for formatting and feature toggles.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml.cs
new file mode 100644
index 0000000000..7648d96f0b
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace ControlCatalog.Pages
+{
+ public partial class CommandBarTogglePage : UserControl
+ {
+ public CommandBarTogglePage()
+ {
+ InitializeComponent();
+ }
+
+ private void OnFormatChanged(object? sender, RoutedEventArgs e)
+ {
+ if (FormatStatus == null)
+ return;
+ var active = new List();
+ if (BoldToggle.IsChecked == true) active.Add("Bold");
+ if (ItalicToggle.IsChecked == true) active.Add("Italic");
+ if (UnderlineToggle.IsChecked == true) active.Add("Underline");
+ FormatStatus.Text = active.Count > 0 ? $"Active: {string.Join(", ", active)}" : "Active: (none)";
+
+ ForceBoldCheck.IsChecked = BoldToggle.IsChecked;
+ ForceItalicCheck.IsChecked = ItalicToggle.IsChecked;
+ ForceUnderlineCheck.IsChecked = UnderlineToggle.IsChecked;
+ }
+
+ private void OnForceBoldChanged(object? sender, RoutedEventArgs e)
+ {
+ if (BoldToggle == null)
+ return;
+ BoldToggle.IsChecked = ForceBoldCheck.IsChecked;
+ }
+
+ private void OnForceItalicChanged(object? sender, RoutedEventArgs e)
+ {
+ if (ItalicToggle == null)
+ return;
+ ItalicToggle.IsChecked = ForceItalicCheck.IsChecked;
+ }
+
+ private void OnForceUnderlineChanged(object? sender, RoutedEventArgs e)
+ {
+ if (UnderlineToggle == null)
+ return;
+ UnderlineToggle.IsChecked = ForceUnderlineCheck.IsChecked;
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/CommandBarPage.xaml b/samples/ControlCatalog/Pages/CommandBarPage.xaml
new file mode 100644
index 0000000000..6cf280ea95
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBarPage.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs
new file mode 100644
index 0000000000..b1e353dd38
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+namespace ControlCatalog.Pages
+{
+ public partial class CommandBarPage : UserControl
+ {
+ private static readonly (string Group, string Title, string Description, Func Factory)[] Demos =
+ {
+ // Overview
+ ("Overview", "First Look", "A CommandBar with primary commands, secondary overflow menu, and custom content area.", () => new CommandBarFirstLookPage()),
+ ("Overview", "Toggle Buttons", "AppBarToggleButton for stateful actions like Bold, Italic, and Favorite.", () => new CommandBarTogglePage()),
+
+ // Appearance
+ ("Appearance", "Label Positions", "Configure label position: Bottom (default), Right, or Collapsed (icon only).", () => new CommandBarLabelPositionPage()),
+ ("Appearance", "Customization", "Background, Foreground, BorderBrush, BorderThickness, and CornerRadius.", () => new CommandBarCustomizationPage()),
+
+ // 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()),
+ };
+
+ public CommandBarPage()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ await SampleNav.PushAsync(CreateHomePage(), null);
+ }
+
+ private ContentPage CreateHomePage()
+ {
+ var stack = new StackPanel
+ {
+ Margin = new Avalonia.Thickness(12),
+ Spacing = 16
+ };
+
+ var groups = new Dictionary();
+ var groupOrder = new List();
+
+ foreach (var (group, title, description, factory) in Demos)
+ {
+ if (!groups.ContainsKey(group))
+ {
+ groups[group] = new WrapPanel
+ {
+ Orientation = Orientation.Horizontal,
+ HorizontalAlignment = HorizontalAlignment.Left
+ };
+ groupOrder.Add(group);
+ }
+
+ var demoFactory = factory;
+ var demoTitle = title;
+
+ var card = new Button
+ {
+ Width = 170,
+ MinHeight = 80,
+ Margin = new Avalonia.Thickness(0, 0, 8, 8),
+ VerticalAlignment = VerticalAlignment.Top,
+ HorizontalContentAlignment = HorizontalAlignment.Left,
+ VerticalContentAlignment = VerticalAlignment.Top,
+ Padding = new Avalonia.Thickness(12, 8),
+ Content = new StackPanel
+ {
+ Spacing = 4,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = title,
+ FontSize = 13,
+ FontWeight = FontWeight.SemiBold,
+ TextWrapping = TextWrapping.Wrap
+ },
+ new TextBlock
+ {
+ Text = description,
+ FontSize = 11,
+ Opacity = 0.6,
+ TextWrapping = TextWrapping.Wrap
+ }
+ }
+ }
+ };
+
+ card.Click += async (s, e) =>
+ {
+ var headerGrid = new Grid { ColumnDefinitions = new ColumnDefinitions("*, Auto") };
+ headerGrid.Children.Add(new TextBlock
+ {
+ Text = demoTitle,
+ VerticalAlignment = VerticalAlignment.Center
+ });
+ var closeBtn = new Button
+ {
+ Content = new PathIcon
+ {
+ Data = 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")
+ },
+ Background = Brushes.Transparent,
+ BorderThickness = new Avalonia.Thickness(0),
+ Padding = new Avalonia.Thickness(8, 4),
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ Grid.SetColumn(closeBtn, 1);
+ headerGrid.Children.Add(closeBtn);
+ closeBtn.Click += async (_, _) => await SampleNav.PopAsync(null);
+
+ var page = new ContentPage
+ {
+ Header = headerGrid,
+ Content = demoFactory(),
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+ NavigationPage.SetHasBackButton(page, false);
+ await SampleNav.PushAsync(page, null);
+ };
+
+ groups[group].Children.Add(card);
+ }
+
+ foreach (var groupName in groupOrder)
+ {
+ stack.Children.Add(new TextBlock
+ {
+ Text = groupName,
+ FontSize = 13,
+ FontWeight = FontWeight.SemiBold,
+ Margin = new Avalonia.Thickness(0, 0, 0, 4),
+ Opacity = 0.6
+ });
+ stack.Children.Add(groups[groupName]);
+ }
+
+ var homePage = new ContentPage
+ {
+ Content = new ScrollViewer { Content = stack },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+
+ NavigationPage.SetHasNavigationBar(homePage, false);
+
+ return homePage;
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml b/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml
new file mode 100644
index 0000000000..bf37b4cc4f
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml.cs b/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml.cs
new file mode 100644
index 0000000000..0c13bb360e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml.cs
@@ -0,0 +1,144 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+namespace ControlCatalog.Pages
+{
+ public partial class ConnectedAnimationDemoPage : UserControl
+ {
+ private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = [];
+
+ public ConnectedAnimationDemoPage()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ await SampleNav.PushAsync(CreateHomePage(), null);
+ }
+
+ private ContentPage CreateHomePage()
+ {
+ var stack = new StackPanel
+ {
+ Margin = new Avalonia.Thickness(12),
+ Spacing = 16
+ };
+
+ var groups = new Dictionary();
+ var groupOrder = new List();
+
+ foreach (var (group, title, description, factory) in Demos)
+ {
+ if (!groups.ContainsKey(group))
+ {
+ groups[group] = new WrapPanel
+ {
+ Orientation = Orientation.Horizontal,
+ HorizontalAlignment = HorizontalAlignment.Left
+ };
+ groupOrder.Add(group);
+ }
+
+ var demoFactory = factory;
+ var demoTitle = title;
+
+ var card = new Button
+ {
+ Width = 200,
+ MinHeight = 80,
+ Margin = new Avalonia.Thickness(0, 0, 8, 8),
+ VerticalAlignment = VerticalAlignment.Top,
+ HorizontalContentAlignment = HorizontalAlignment.Left,
+ VerticalContentAlignment = VerticalAlignment.Top,
+ Padding = new Avalonia.Thickness(12, 8),
+ Content = new StackPanel
+ {
+ Spacing = 4,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = title,
+ FontSize = 13,
+ FontWeight = FontWeight.SemiBold,
+ TextWrapping = TextWrapping.Wrap
+ },
+ new TextBlock
+ {
+ Text = description,
+ FontSize = 11,
+ Opacity = 0.6,
+ TextWrapping = TextWrapping.Wrap
+ }
+ }
+ }
+ };
+
+ card.Click += async (s, ev) =>
+ {
+ var headerGrid = new Grid { ColumnDefinitions = new ColumnDefinitions("*, Auto") };
+ headerGrid.Children.Add(new TextBlock
+ {
+ Text = demoTitle,
+ VerticalAlignment = VerticalAlignment.Center
+ });
+ var closeBtn = new Button
+ {
+ Content = new PathIcon
+ {
+ Data = 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")
+ },
+ Background = Brushes.Transparent,
+ BorderThickness = new Avalonia.Thickness(0),
+ Padding = new Avalonia.Thickness(8, 4),
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ Grid.SetColumn(closeBtn, 1);
+ headerGrid.Children.Add(closeBtn);
+ closeBtn.Click += async (_, _) => await SampleNav.PopAsync(null);
+
+ var page = new ContentPage
+ {
+ Header = headerGrid,
+ Content = demoFactory(),
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+ NavigationPage.SetHasBackButton(page, false);
+ await SampleNav.PushAsync(page, null);
+ };
+
+ groups[group].Children.Add(card);
+ }
+
+ foreach (var groupName in groupOrder)
+ {
+ stack.Children.Add(new TextBlock
+ {
+ Text = groupName,
+ FontSize = 13,
+ FontWeight = FontWeight.SemiBold,
+ Margin = new Avalonia.Thickness(0, 0, 0, 4),
+ Opacity = 0.6
+ });
+ stack.Children.Add(groups[groupName]);
+ }
+
+ var homePage = new ContentPage
+ {
+ Content = new ScrollViewer { Content = stack },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+
+ NavigationPage.SetHasNavigationBar(homePage, false);
+ return homePage;
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ContentDemoPage.xaml b/samples/ControlCatalog/Pages/ContentDemoPage.xaml
new file mode 100644
index 0000000000..d8f2155a1b
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentDemoPage.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ContentDemoPage.xaml.cs b/samples/ControlCatalog/Pages/ContentDemoPage.xaml.cs
new file mode 100644
index 0000000000..e667cfc0ff
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentDemoPage.xaml.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+namespace ControlCatalog.Pages
+{
+ public partial class ContentDemoPage : UserControl
+ {
+ private static readonly (string Group, string Title, string Description, Func Factory)[] Demos =
+ {
+ // Overview
+ ("Overview", "First Look", "Basic ContentPage with header, content, and background inside a NavigationPage.", () => new ContentPageFirstLookPage()),
+
+ // Appearance
+ ("Appearance", "Customization", "Adjust content alignment, background color, and padding to understand how ContentPage adapts its layout.", () => new ContentPageCustomizationPage()),
+
+ // Features
+ ("Features", "CommandBar", "Attach a CommandBar to the top or bottom of a ContentPage. Add and remove items at runtime.", () => new ContentPageCommandBarPage()),
+ ("Features", "Safe Area", "Understand how AutomaticallyApplySafeAreaPadding absorbs platform insets.", () => new ContentPageSafeAreaPage()),
+ ("Features", "Events", "Observe page lifecycle events: NavigatedTo, NavigatedFrom, and Navigating.", () => new ContentPageEventsPage()),
+
+ // Performance
+ ("Performance", "Performance Monitor", "Push ContentPages of varying weight (50 KB to 2 MB) and observe live instance count and managed heap. Pop pages and force GC to confirm memory is released.", () => new ContentPagePerformancePage()),
+ };
+
+ public ContentDemoPage()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ await SampleNav.PushAsync(CreateHomePage(), null);
+ }
+
+ private ContentPage CreateHomePage()
+ {
+ var stack = new StackPanel
+ {
+ Margin = new Avalonia.Thickness(12),
+ Spacing = 16
+ };
+
+ var groups = new Dictionary();
+ var groupOrder = new List();
+
+ foreach (var (group, title, description, factory) in Demos)
+ {
+ if (!groups.ContainsKey(group))
+ {
+ groups[group] = new WrapPanel
+ {
+ Orientation = Orientation.Horizontal,
+ HorizontalAlignment = HorizontalAlignment.Left
+ };
+ groupOrder.Add(group);
+ }
+
+ var demoFactory = factory;
+ var demoTitle = title;
+
+ var card = new Button
+ {
+ Width = 170,
+ MinHeight = 80,
+ Margin = new Avalonia.Thickness(0, 0, 8, 8),
+ VerticalAlignment = VerticalAlignment.Top,
+ HorizontalContentAlignment = HorizontalAlignment.Left,
+ VerticalContentAlignment = VerticalAlignment.Top,
+ Padding = new Avalonia.Thickness(12, 8),
+ Content = new StackPanel
+ {
+ Spacing = 4,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = title,
+ FontSize = 13,
+ FontWeight = FontWeight.SemiBold,
+ TextWrapping = TextWrapping.Wrap
+ },
+ new TextBlock
+ {
+ Text = description,
+ FontSize = 11,
+ Opacity = 0.6,
+ TextWrapping = TextWrapping.Wrap
+ }
+ }
+ }
+ };
+
+ card.Click += async (s, e) =>
+ {
+ var headerGrid = new Grid { ColumnDefinitions = new ColumnDefinitions("*, Auto") };
+ headerGrid.Children.Add(new TextBlock
+ {
+ Text = demoTitle,
+ VerticalAlignment = VerticalAlignment.Center
+ });
+ var closeBtn = new Button
+ {
+ Content = new PathIcon
+ {
+ Data = 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")
+ },
+ Background = Brushes.Transparent,
+ BorderThickness = new Avalonia.Thickness(0),
+ Padding = new Avalonia.Thickness(8, 4),
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ Grid.SetColumn(closeBtn, 1);
+ headerGrid.Children.Add(closeBtn);
+ closeBtn.Click += async (_, _) => await SampleNav.PopAsync(null);
+
+ var page = new ContentPage
+ {
+ Header = headerGrid,
+ Content = demoFactory(),
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+ NavigationPage.SetHasBackButton(page, false);
+ await SampleNav.PushAsync(page, null);
+ };
+
+ groups[group].Children.Add(card);
+ }
+
+ foreach (var groupName in groupOrder)
+ {
+ stack.Children.Add(new TextBlock
+ {
+ Text = groupName,
+ FontSize = 13,
+ FontWeight = FontWeight.SemiBold,
+ Margin = new Avalonia.Thickness(0, 0, 0, 4),
+ Opacity = 0.6
+ });
+ stack.Children.Add(groups[groupName]);
+ }
+
+ var homePage = new ContentPage
+ {
+ Content = new ScrollViewer { Content = stack },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+
+ NavigationPage.SetHasNavigationBar(homePage, false);
+
+ return homePage;
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml b/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml
new file mode 100644
index 0000000000..b5fed22feb
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml.cs b/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml.cs
new file mode 100644
index 0000000000..07bdbb52a2
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml.cs
@@ -0,0 +1,212 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+namespace ControlCatalog.Pages
+{
+ public partial class ContentPageCommandBarPage : UserControl
+ {
+ private static readonly (string Label, string PathData)[] IconPresets =
+ {
+ ("Add", "M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"),
+ ("Save", "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"),
+ ("Share", "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"),
+ ("Favorite", "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"),
+ ("Delete", "M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"),
+ };
+
+ private int _itemCounter;
+ private string _position = "Top";
+ private readonly List _primaryItems = new();
+ private readonly List _secondaryItems = new();
+
+ public ContentPageCommandBarPage()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ var rootPage = new ContentPage
+ {
+ Header = "CommandBar Demo",
+ Background = new SolidColorBrush(Color.Parse("#E3F2FD")),
+ Content = new StackPanel
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Spacing = 12,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = "Root Page",
+ FontSize = 24,
+ FontWeight = FontWeight.Bold,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ },
+ new TextBlock
+ {
+ Text = "Add items using the panel on the right,\nthen change the position to Top or Bottom.",
+ FontSize = 14,
+ TextWrapping = TextWrapping.Wrap,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ TextAlignment = TextAlignment.Center,
+ Opacity = 0.7,
+ }
+ }
+ },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+ await DemoNav.PushAsync(rootPage);
+ }
+
+ private void OnPositionChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (PositionCombo == null)
+ return;
+ _position = PositionCombo.SelectedIndex == 1 ? "Bottom" : "Top";
+ RebuildCommandBar();
+ }
+
+ private void OnAddPrimary(object? sender, RoutedEventArgs e)
+ {
+ _itemCounter++;
+ var btn = new AppBarButton { Label = $"Action {_itemCounter}" };
+ if (UseIconCheck.IsChecked == true)
+ {
+ var preset = IconPresets[(_itemCounter - 1) % IconPresets.Length];
+ btn.Label = preset.Label;
+ btn.Icon = new PathIcon { Data = StreamGeometry.Parse(preset.PathData) };
+ btn.IsCompact = true;
+ }
+ _primaryItems.Add(btn);
+ RebuildCommandBar();
+ }
+
+ private void OnAddSecondary(object? sender, RoutedEventArgs e)
+ {
+ _itemCounter++;
+ _secondaryItems.Add(new AppBarButton { Label = $"Item {_itemCounter}" });
+ RebuildCommandBar();
+ }
+
+ private void OnAddSeparator(object? sender, RoutedEventArgs e)
+ {
+ _primaryItems.Add(new AppBarSeparator());
+ RebuildCommandBar();
+ }
+
+ private void OnClearAll(object? sender, RoutedEventArgs e)
+ {
+ _primaryItems.Clear();
+ _secondaryItems.Clear();
+ _itemCounter = 0;
+ ClearCommandBarFromActivePage();
+ StatusText.Text = "No items added";
+ }
+
+ private async void OnPush(object? sender, RoutedEventArgs e)
+ {
+ var next = DemoNav.StackDepth + 1;
+ var page = new ContentPage
+ {
+ Header = $"Page {next}",
+ Background = new SolidColorBrush(Color.Parse("#E8F5E9")),
+ Content = new StackPanel
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Spacing = 8,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = $"Page {next}",
+ FontSize = 28,
+ FontWeight = FontWeight.Bold,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ },
+ new TextBlock
+ {
+ Text = "New page: no CommandBar set",
+ FontSize = 14,
+ Opacity = 0.6,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ }
+ }
+ },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+ await DemoNav.PushAsync(page);
+ }
+
+ private async void OnPop(object? sender, RoutedEventArgs e) => await DemoNav.PopAsync();
+
+ private void ClearCommandBarFromActivePage()
+ {
+ if (DemoNav.CurrentPage is Page activePage)
+ {
+ NavigationPage.SetTopCommandBar(activePage, null);
+ NavigationPage.SetBottomCommandBar(activePage, null);
+ }
+ }
+
+ private void RebuildCommandBar()
+ {
+ if (DemoNav == null || DemoNav.CurrentPage is not Page activePage)
+ return;
+ if (_primaryItems.Count == 0 && _secondaryItems.Count == 0)
+ {
+ ClearCommandBarFromActivePage();
+ StatusText.Text = "No items added";
+ return;
+ }
+
+ NavigationPage.SetTopCommandBar(activePage, null);
+ NavigationPage.SetBottomCommandBar(activePage, null);
+
+ var commandBar = new CommandBar { IsDynamicOverflowEnabled = true };
+
+ foreach (var item in _primaryItems)
+ {
+ if (item is AppBarButton btn)
+ {
+ PathIcon? icon = null;
+ if (btn.Icon is PathIcon src)
+ icon = new PathIcon { Data = src.Data };
+ commandBar.PrimaryCommands.Add(new AppBarButton
+ {
+ Label = btn.Label,
+ Icon = icon,
+ IsCompact = btn.IsCompact,
+ });
+ }
+ else if (item is AppBarSeparator)
+ commandBar.PrimaryCommands.Add(new AppBarSeparator());
+ }
+
+ foreach (var item in _secondaryItems)
+ {
+ if (item is AppBarButton btn)
+ commandBar.SecondaryCommands.Add(new AppBarButton { Label = btn.Label });
+ }
+
+ if (_position == "Top")
+ NavigationPage.SetTopCommandBar(activePage, commandBar);
+ else
+ NavigationPage.SetBottomCommandBar(activePage, commandBar);
+
+ var primaryCount = _primaryItems.Count(i => i is AppBarButton);
+ var secondaryCount = _secondaryItems.Count;
+ StatusText.Text = $"{primaryCount} primary, {secondaryCount} secondary ({_position})";
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageCustomizationPage.xaml b/samples/ControlCatalog/Pages/ContentPage/ContentPageCustomizationPage.xaml
new file mode 100644
index 0000000000..ea3ba8281d
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageCustomizationPage.xaml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageCustomizationPage.xaml.cs b/samples/ControlCatalog/Pages/ContentPage/ContentPageCustomizationPage.xaml.cs
new file mode 100644
index 0000000000..2ce23b37cb
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageCustomizationPage.xaml.cs
@@ -0,0 +1,64 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+namespace ControlCatalog.Pages
+{
+ public partial class ContentPageCustomizationPage : UserControl
+ {
+ public ContentPageCustomizationPage()
+ {
+ InitializeComponent();
+ }
+
+ private void OnBackgroundChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (SamplePage == null)
+ return;
+ SamplePage.Background = BackgroundCombo.SelectedIndex switch
+ {
+ 1 => new SolidColorBrush(Color.Parse("#E3F2FD")),
+ 2 => new SolidColorBrush(Color.Parse("#E8F5E9")),
+ 3 => new SolidColorBrush(Color.Parse("#F3E5F5")),
+ 4 => new SolidColorBrush(Color.Parse("#FFF8E1")),
+ _ => null
+ };
+ }
+
+ private void OnHAlignChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (SamplePage == null)
+ return;
+ SamplePage.HorizontalContentAlignment = HAlignCombo.SelectedIndex switch
+ {
+ 0 => HorizontalAlignment.Left,
+ 1 => HorizontalAlignment.Center,
+ 2 => HorizontalAlignment.Right,
+ _ => HorizontalAlignment.Stretch
+ };
+ }
+
+ private void OnVAlignChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (SamplePage == null)
+ return;
+ SamplePage.VerticalContentAlignment = VAlignCombo.SelectedIndex switch
+ {
+ 0 => VerticalAlignment.Top,
+ 1 => VerticalAlignment.Center,
+ 2 => VerticalAlignment.Bottom,
+ _ => VerticalAlignment.Stretch
+ };
+ }
+
+ private void OnPaddingChanged(object? sender, Avalonia.Controls.Primitives.RangeBaseValueChangedEventArgs e)
+ {
+ if (SamplePage == null)
+ return;
+ var padding = (int)PaddingSlider.Value;
+ SamplePage.Padding = new Avalonia.Thickness(padding);
+ PaddingLabel.Text = $"{padding} px";
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageEventsPage.xaml b/samples/ControlCatalog/Pages/ContentPage/ContentPageEventsPage.xaml
new file mode 100644
index 0000000000..f860addefa
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageEventsPage.xaml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageEventsPage.xaml.cs b/samples/ControlCatalog/Pages/ContentPage/ContentPageEventsPage.xaml.cs
new file mode 100644
index 0000000000..4a0ff2809b
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageEventsPage.xaml.cs
@@ -0,0 +1,103 @@
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+namespace ControlCatalog.Pages
+{
+ public partial class ContentPageEventsPage : UserControl
+ {
+ private int _pageCount;
+ private readonly ObservableCollection _logItems = new();
+
+ public ContentPageEventsPage()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ EventLogItems.ItemsSource = _logItems;
+
+ var root = MakePage("Root", "Navigate to see lifecycle events in the log below.");
+ SubscribeEvents(root);
+ await DemoNav.PushAsync(root);
+ }
+
+ private void SubscribeEvents(ContentPage page)
+ {
+ page.NavigatedTo += (_, e) => AddLog($"[{page.Header}] NavigatedTo (type: {e.NavigationType})");
+ page.NavigatedFrom += (_, _) => AddLog($"[{page.Header}] NavigatedFrom");
+ page.Navigating += async args =>
+ {
+ if (BlockNavCheck.IsChecked == true)
+ {
+ args.Cancel = true;
+ AddLog($"[{page.Header}] Navigating โ BLOCKED");
+ await Task.Delay(600);
+ args.Cancel = false;
+ AddLog($"[{page.Header}] Navigating โ unblocked");
+ }
+ else
+ {
+ AddLog($"[{page.Header}] Navigating");
+ }
+ };
+ }
+
+ private async void OnPush(object? sender, RoutedEventArgs e)
+ {
+ _pageCount++;
+ var page = MakePage($"Page {_pageCount}", "Navigate back to see Navigating/NavigatedFrom.");
+ SubscribeEvents(page);
+ await DemoNav.PushAsync(page);
+ }
+
+ private async void OnPop(object? sender, RoutedEventArgs e) => await DemoNav.PopAsync();
+
+ private void OnClearLog(object? sender, RoutedEventArgs e) => _logItems.Clear();
+
+ private void AddLog(string message)
+ {
+ _logItems.Add(message);
+ LogScrollViewer.ScrollToEnd();
+ }
+
+ private static ContentPage MakePage(string header, string body) =>
+ new ContentPage
+ {
+ Header = header,
+ Content = new StackPanel
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Spacing = 8,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = header,
+ FontSize = 24,
+ FontWeight = FontWeight.Bold,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ },
+ new TextBlock
+ {
+ Text = body,
+ FontSize = 13,
+ Opacity = 0.6,
+ TextWrapping = TextWrapping.Wrap,
+ TextAlignment = TextAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ MaxWidth = 260,
+ }
+ }
+ },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch,
+ };
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageFirstLookPage.xaml b/samples/ControlCatalog/Pages/ContentPage/ContentPageFirstLookPage.xaml
new file mode 100644
index 0000000000..0c18ceebc2
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageFirstLookPage.xaml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageFirstLookPage.xaml.cs b/samples/ControlCatalog/Pages/ContentPage/ContentPageFirstLookPage.xaml.cs
new file mode 100644
index 0000000000..ec5057e5e6
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageFirstLookPage.xaml.cs
@@ -0,0 +1,93 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+namespace ControlCatalog.Pages
+{
+ public partial class ContentPageFirstLookPage : UserControl
+ {
+ private static readonly Color[] PageColors =
+ [
+ Color.FromRgb(0xE3, 0xF2, 0xFD), // blue
+ Color.FromRgb(0xF3, 0xE5, 0xF5), // purple
+ Color.FromRgb(0xE8, 0xF5, 0xE9), // green
+ Color.FromRgb(0xFF, 0xF8, 0xE1), // amber
+ Color.FromRgb(0xFB, 0xE9, 0xE7), // deep orange
+ ];
+
+ private int _pageCount;
+
+ public ContentPageFirstLookPage()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ await DemoNav.PushAsync(MakePage("Root Page", "ContentPage inside a NavigationPage.\nUse the options to navigate."));
+ UpdateStatus();
+ }
+
+ private async void OnPush(object? sender, RoutedEventArgs e)
+ {
+ _pageCount++;
+ await DemoNav.PushAsync(MakePage($"Page {_pageCount}", $"ContentPage #{_pageCount}.\nNavigate back using the back button."));
+ UpdateStatus();
+ }
+
+ private async void OnPop(object? sender, RoutedEventArgs e)
+ {
+ await DemoNav.PopAsync();
+ UpdateStatus();
+ }
+
+ private async void OnPopToRoot(object? sender, RoutedEventArgs e)
+ {
+ await DemoNav.PopToRootAsync();
+ _pageCount = 0;
+ UpdateStatus();
+ }
+
+ private void UpdateStatus()
+ {
+ StatusText.Text = $"Depth: {DemoNav.StackDepth} | Current: {DemoNav.CurrentPage?.Header}";
+ }
+
+ private ContentPage MakePage(string header, string body) =>
+ new ContentPage
+ {
+ Header = header,
+ Background = new SolidColorBrush(PageColors[_pageCount % PageColors.Length]),
+ Content = new StackPanel
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Spacing = 10,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = header,
+ FontSize = 20,
+ FontWeight = FontWeight.SemiBold,
+ HorizontalAlignment = HorizontalAlignment.Center
+ },
+ new TextBlock
+ {
+ Text = body,
+ FontSize = 13,
+ Opacity = 0.7,
+ TextWrapping = TextWrapping.Wrap,
+ TextAlignment = TextAlignment.Center,
+ MaxWidth = 260
+ }
+ }
+ },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPagePerformancePage.xaml b/samples/ControlCatalog/Pages/ContentPage/ContentPagePerformancePage.xaml
new file mode 100644
index 0000000000..b3ec61383e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPagePerformancePage.xaml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPagePerformancePage.xaml.cs b/samples/ControlCatalog/Pages/ContentPage/ContentPagePerformancePage.xaml.cs
new file mode 100644
index 0000000000..140db27590
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPagePerformancePage.xaml.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace ControlCatalog.Pages
+{
+ public partial class ContentPagePerformancePage : UserControl
+ {
+ private static readonly int[] AllocationSizes = [51_200, 512_000, 2_097_152];
+
+ private readonly NavigationPerformanceMonitorHelper _perf = new();
+ private int _pageCounter;
+
+ private readonly List<(Border Container, Border Badge, TextBlock IndexText,
+ TextBlock TitleText, TextBlock BadgeText)> _stackRowCache = new();
+
+ public ContentPagePerformancePage()
+ {
+ InitializeComponent();
+ }
+
+ protected override async void OnLoaded(RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+
+ NavPage.Pushed += OnStackChanged;
+ NavPage.Popped += OnStackChanged;
+ NavPage.PoppedToRoot += OnStackChanged;
+
+ _perf.InitHeap();
+ _pageCounter++;
+ await NavPage.PushAsync(BuildPage("Home", _pageCounter));
+ Log("Init", "Pushed root page");
+ }
+
+ protected override void OnUnloaded(RoutedEventArgs e)
+ {
+ base.OnUnloaded(e);
+ _perf.StopAutoRefresh();
+ }
+
+ private void OnStackChanged(object? sender, NavigationEventArgs e) => RefreshAll();
+
+ private async void OnPush(object? sender, RoutedEventArgs e)
+ {
+ _pageCounter++;
+ var page = BuildPage($"Page {_pageCounter}", _pageCounter);
+ await NavPage.PushAsync(page);
+ Log("Push", $"Pushed \"{page.Header}\"");
+ }
+
+ private async void OnPush5(object? sender, RoutedEventArgs e)
+ {
+ int first = _pageCounter + 1;
+ for (int i = 0; i < 5; i++)
+ {
+ _pageCounter++;
+ await NavPage.PushAsync(BuildPage($"Page {_pageCounter}", _pageCounter));
+ }
+ Log("Push ร5", $"Pushed pages {first}โ{_pageCounter}");
+ }
+
+ private async void OnPop(object? sender, RoutedEventArgs e)
+ {
+ if (NavPage.StackDepth > 1)
+ {
+ var popped = NavPage.CurrentPage;
+ await NavPage.PopAsync();
+ Log("Pop", $"Popped \"{popped?.Header}\"");
+ }
+ }
+
+ private async void OnPopToRoot(object? sender, RoutedEventArgs e)
+ {
+ if (NavPage.StackDepth > 1)
+ {
+ int removed = NavPage.StackDepth - 1;
+ await NavPage.PopToRootAsync();
+ Log("PopToRoot", $"Removed {removed} page(s)");
+ }
+ }
+
+ private void OnForceGC(object? sender, RoutedEventArgs e)
+ {
+ _perf.ForceGC(RefreshAll);
+ Log("GC", "Forced full garbage collection");
+ }
+
+ private void OnClearLog(object? sender, RoutedEventArgs e) => LogPanel.Children.Clear();
+
+ private void OnAutoRefreshChanged(object? sender, RoutedEventArgs e) =>
+ _perf.OnAutoRefreshChanged(AutoRefreshCheck, RefreshAll);
+
+ private void RefreshAll()
+ {
+ StackDepthText.Text = $"Stack Depth: {NavPage.StackDepth}";
+ LiveInstancesText.Text = $"Live Page Instances: {_perf.CountLiveInstances()}";
+ TotalCreatedText.Text = $"Total Pages Created: {_perf.TotalCreated}";
+ _perf.UpdateHeapDelta(ManagedMemoryText, MemoryDeltaText);
+
+ NavigationPerformanceMonitorHelper.RefreshStackPanel(
+ StackVisPanel, _stackRowCache,
+ NavPage.NavigationStack, NavPage.CurrentPage);
+ }
+
+ private void Log(string action, string detail) =>
+ _perf.LogOperation(action, detail, LogPanel, LogScrollViewer,
+ $"depth {NavPage.StackDepth}");
+
+ private ContentPage BuildPage(string title, int index)
+ {
+ var weightIndex = WeightCombo.SelectedIndex >= 0 ? WeightCombo.SelectedIndex : 0;
+ var allocBytes = AllocationSizes[Math.Clamp(weightIndex, 0, AllocationSizes.Length - 1)];
+ var weightLabel = weightIndex switch { 1 => "~500 KB", 2 => "~2 MB", _ => "~50 KB" };
+
+ var page = NavigationDemoHelper.MakePage(title, $"Stack position #{index} ยท Weight: {weightLabel}\n\n" +
+ "Pop this page and force GC to see the heap drop by the weight shown above. " +
+ "Live Instances decreases once the GC finalizes the page.", index);
+ page.Tag = new byte[allocBytes];
+ _perf.TrackPage(page);
+ return page;
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageSafeAreaPage.xaml b/samples/ControlCatalog/Pages/ContentPage/ContentPageSafeAreaPage.xaml
new file mode 100644
index 0000000000..da01d99d6e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageSafeAreaPage.xaml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+ When enabled, ContentPage absorbs SafeAreaPadding into the content
+ presenter, keeping page content in the visible area. Set to false
+ to manage insets manually.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ โข On real devices SafeAreaPadding is set by PageNavigationHost
+ from IInsetsManager (iOS, Android).
+
+
+ โข ContentPage.AutomaticallyApplySafeAreaPadding defaults to true.
+ Set to false when your layout manages insets manually.
+
+
+ โข Child pages inside a ContentPage receive the remaining insets
+ via SafeAreaPadding propagation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ When enabled, the content presenter absorbs the inset,
+ keeping content visible above the status bar and home indicator.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageSafeAreaPage.xaml.cs b/samples/ControlCatalog/Pages/ContentPage/ContentPageSafeAreaPage.xaml.cs
new file mode 100644
index 0000000000..93974f4741
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageSafeAreaPage.xaml.cs
@@ -0,0 +1,70 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+
+namespace ControlCatalog.Pages
+{
+ public partial class ContentPageSafeAreaPage : UserControl
+ {
+ public ContentPageSafeAreaPage()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ SyncIndicators();
+ }
+
+ private void OnAutoApplyChanged(object? sender, RoutedEventArgs e)
+ {
+ if (SamplePage == null)
+ return;
+ SamplePage.AutomaticallyApplySafeAreaPadding = AutoApplyCheck.IsChecked == true;
+ SyncIndicators();
+ }
+
+ private void OnInsetChanged(object? sender, RangeBaseValueChangedEventArgs e)
+ {
+ SyncIndicators();
+ }
+
+ private void SyncIndicators()
+ {
+ if (SamplePage == null)
+ return;
+ var top = (int)TopSlider.Value;
+ var bottom = (int)BottomSlider.Value;
+ var left = (int)LeftSlider.Value;
+ var right = (int)RightSlider.Value;
+
+ TopValue.Text = $"{top}";
+ BottomValue.Text = $"{bottom}";
+ LeftValue.Text = $"{left}";
+ RightValue.Text = $"{right}";
+
+ TopInsetIndicator.IsVisible = top > 0;
+ TopInsetIndicator.Height = top;
+
+ BottomInsetIndicator.IsVisible = bottom > 0;
+ BottomInsetIndicator.Height = bottom;
+
+ LeftInsetIndicator.IsVisible = left > 0;
+ LeftInsetIndicator.Width = left;
+ LeftInsetIndicator.Margin = new Thickness(0, top, 0, bottom);
+
+ RightInsetIndicator.IsVisible = right > 0;
+ RightInsetIndicator.Width = right;
+ RightInsetIndicator.Margin = new Thickness(0, top, 0, bottom);
+
+ var insets = new Thickness(left, top, right, bottom);
+ SamplePage.SafeAreaPadding = insets;
+
+ SafeAreaInfo.Text = $"SafeAreaPadding: L={left} T={top} R={right} B={bottom}";
+ AutoApplyInfo.Text = $"AutoApply: {SamplePage.AutomaticallyApplySafeAreaPadding} โ " +
+ (SamplePage.AutomaticallyApplySafeAreaPadding ? "insets absorbed by presenter" : "insets ignored");
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/DrawerDemoPage.xaml b/samples/ControlCatalog/Pages/DrawerDemoPage.xaml
new file mode 100644
index 0000000000..671a9a8487
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DrawerDemoPage.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/DrawerDemoPage.xaml.cs b/samples/ControlCatalog/Pages/DrawerDemoPage.xaml.cs
new file mode 100644
index 0000000000..d0c484cd2b
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DrawerDemoPage.xaml.cs
@@ -0,0 +1,73 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace ControlCatalog.Pages
+{
+ public partial class DrawerDemoPage : UserControl
+ {
+ private static readonly (string Group, string Title, string Description, Func Factory)[] Demos =
+ {
+ // Overview
+ ("Overview", "First Look", "Basic DrawerPage with a navigation drawer, menu items, and detail content.",
+ () => new DrawerPageFirstLookPage()),
+
+ // Features
+ ("Features", "Navigation",
+ "Master-detail pattern: select a drawer menu item to navigate the detail via a NavigationPage with hamburger-to-back-button transition.",
+ () => new DrawerPageNavigationPage()),
+ ("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", "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()),
+ ("Features", "RTL Layout", "Right-to-left layout: drawer opens from the right edge with mirrored gestures.",
+ () => new DrawerPageRtlPage()),
+
+ // Appearance
+ ("Appearance", "Customization",
+ "Customize drawer behavior, layout mode, length, colors, header and footer.",
+ () => new DrawerPageCustomizationPage()),
+ ("Appearance", "Custom Flyout",
+ "Dark overlay menu with staggered item animations and CrossFade page transitions on the detail NavigationPage.",
+ () => new DrawerPageCustomFlyoutPage()),
+ ("Appearance", "Transitions",
+ "Configure the detail NavigationPage transition. Choose CrossFade, PageSlide, or CompositePageTransition to animate detail page changes.",
+ () => new DrawerPageTransitionsPage()),
+
+ // Performance
+ ("Performance", "Performance Monitor",
+ "Track detail page swaps, live page instances, and managed heap size. Observe how GC reclaims memory after swapping pages.",
+ () => new DrawerPagePerformancePage()),
+
+ // Showcases
+ ("Showcases", "AvaloniaFlix",
+ "Streaming app with DrawerPage wrapping NavigationPage. Hamburger auto-injected at root, back arrow on detail, and dark themed flyout menu.",
+ () => new AvaloniaFlixAppPage()),
+ ("Showcases", "L'Avenir Restaurant",
+ "Restaurant app with DrawerPage as the root container, NavigationPage for detail navigation, and TabbedPage bottom tabs for Menu, Reservations, and Profile.",
+ () => new LAvenirAppPage()),
+ ("Showcases", "EcoTracker",
+ "Sustainability tracker with CompactInline drawer, eco leaf hamburger icon, crossfade compact/open menu transitions, and green-themed Home, Stats, Habits, and Community pages.",
+ () => new EcoTrackerAppPage()),
+ ("Showcases", "ModernApp",
+ "Travel social app using a top-placement DrawerPage. A slide-down nav pane gives access to Discover, My Trips, Profile, and Settings. Features destination cards, story circles, an experience feed, a stats profile, and a travel gallery.",
+ () => new ModernAppPage()),
+ ("Showcases", "Controls Gallery App",
+ "Controls gallery app using DrawerPage CompactInline mode. Dark Fluent palette, accent pill selection indicator, search box that fades in when open, expandable category groups, and Settings pinned to the footer.",
+ () => new ControlsGalleryAppPage()),
+ };
+
+ public DrawerDemoPage()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ await SampleNav.PushAsync(NavigationDemoHelper.CreateGalleryHomePage(SampleNav, Demos), null);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/DrawerPage/ControlsGalleryAppPage.xaml b/samples/ControlCatalog/Pages/DrawerPage/ControlsGalleryAppPage.xaml
new file mode 100644
index 0000000000..e0ce3ca03e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DrawerPage/ControlsGalleryAppPage.xaml
@@ -0,0 +1,400 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/DrawerPage/ControlsGalleryAppPage.xaml.cs b/samples/ControlCatalog/Pages/DrawerPage/ControlsGalleryAppPage.xaml.cs
new file mode 100644
index 0000000000..6c771f3078
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DrawerPage/ControlsGalleryAppPage.xaml.cs
@@ -0,0 +1,464 @@
+using System;
+using Avalonia;
+using Avalonia.Animation;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+namespace ControlCatalog.Pages;
+
+public partial class ControlsGalleryAppPage : UserControl
+{
+ static readonly Color Accent = Color.Parse("#60CDFF");
+ static readonly Color ContentBg = Color.Parse("#141414");
+ static readonly Color CardBg = Color.Parse("#1F1F1F");
+ static readonly Color BorderCol = Color.Parse("#2EFFFFFF");
+ static readonly Color TextCol = Color.Parse("#FFFFFF");
+ static readonly Color TextSec = Color.Parse("#C8FFFFFF");
+ static readonly Color TextMuted = Color.Parse("#80FFFFFF");
+
+ DrawerPage? _drawer;
+ NavigationPage? _detailNav;
+ Button? _selectedBtn;
+ TextBox? _searchBox;
+ ContentPage? _preSearchPage;
+ bool _isSearching;
+
+ public ControlsGalleryAppPage()
+ {
+ InitializeComponent();
+
+ _drawer = this.FindControl("NavDrawer");
+ _detailNav = this.FindControl("DetailNav");
+ _selectedBtn = this.FindControl