diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index c03d1fe6cc..b2a81dd55d 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -991,6 +991,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.CrossAxisCancelThresholdProperty + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + F:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.EdgeSizeProperty + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 F:Avalonia.Input.HoldingState.Cancelled @@ -1147,6 +1159,30 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.get_CrossAxisCancelThreshold + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.get_EdgeSize + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.set_CrossAxisCancelThreshold(System.Double) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.set_EdgeSize(System.Double) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.HoldingRoutedEventArgs.#ctor(Avalonia.Input.HoldingState,Avalonia.Point,Avalonia.Input.PointerType) @@ -1411,6 +1447,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.SwipeGestureEventArgs.#ctor(System.Int32,Avalonia.Input.SwipeDirection,Avalonia.Vector,Avalonia.Point) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.SwipeGestureEventArgs.get_StartPoint + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel @@ -1801,6 +1849,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.DrawerPage.DrawerBreakpointWidthProperty + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.NativeMenuBar.EnableMenuItemClickForwardingProperty @@ -1813,6 +1867,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.Primitives.FlyoutBase.IsOpenProperty + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.Primitives.Popup.PlacementModeProperty @@ -1843,6 +1903,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.TabItem.IconProperty + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.TextBlock.LetterSpacingProperty @@ -1981,6 +2047,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.DrawerPage.get_DrawerBreakpointWidth + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.DrawerPage.set_DrawerBreakpointWidth(System.Double) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.get_Surfaces @@ -2017,6 +2095,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.PageSelectionChangedEventArgs.#ctor(Avalonia.Controls.Page,Avalonia.Controls.Page) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Platform.DefaultMenuInteractionHandler.GotFocus(System.Object,Avalonia.Input.GotFocusEventArgs) @@ -2221,6 +2305,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Primitives.VisualLayerManager.get_IsPopup + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Primitives.VisualLayerManager.get_LightDismissOverlayLayer @@ -2239,12 +2329,30 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Primitives.VisualLayerManager.set_IsPopup(System.Boolean) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Screens.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.TabbedPage.FindNextEnabledTab(System.Int32,System.Int32) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.TabItem.get_Icon + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.TabItem.SubscribeToOwnerProperties(Avalonia.AvaloniaObject) @@ -2545,6 +2653,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.CrossAxisCancelThresholdProperty + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + F:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.EdgeSizeProperty + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 F:Avalonia.Input.HoldingState.Cancelled @@ -2701,6 +2821,30 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.get_CrossAxisCancelThreshold + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.get_EdgeSize + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.set_CrossAxisCancelThreshold(System.Double) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.GestureRecognizers.SwipeGestureRecognizer.set_EdgeSize(System.Double) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.HoldingRoutedEventArgs.#ctor(Avalonia.Input.HoldingState,Avalonia.Point,Avalonia.Input.PointerType) @@ -2965,6 +3109,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.SwipeGestureEventArgs.#ctor(System.Int32,Avalonia.Input.SwipeDirection,Avalonia.Vector,Avalonia.Point) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.SwipeGestureEventArgs.get_StartPoint + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel @@ -3355,6 +3511,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.DrawerPage.DrawerBreakpointWidthProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.NativeMenuBar.EnableMenuItemClickForwardingProperty @@ -3367,6 +3529,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.Primitives.FlyoutBase.IsOpenProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.Primitives.Popup.PlacementModeProperty @@ -3397,6 +3565,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.TabItem.IconProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.TextBlock.LetterSpacingProperty @@ -3535,6 +3709,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.DrawerPage.get_DrawerBreakpointWidth + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.DrawerPage.set_DrawerBreakpointWidth(System.Double) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.get_Surfaces @@ -3571,6 +3757,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.PageSelectionChangedEventArgs.#ctor(Avalonia.Controls.Page,Avalonia.Controls.Page) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Platform.DefaultMenuInteractionHandler.GotFocus(System.Object,Avalonia.Input.GotFocusEventArgs) @@ -3775,6 +3967,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Primitives.VisualLayerManager.get_IsPopup + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Primitives.VisualLayerManager.get_LightDismissOverlayLayer @@ -3793,12 +3991,30 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Primitives.VisualLayerManager.set_IsPopup(System.Boolean) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Screens.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.TabbedPage.FindNextEnabledTab(System.Int32,System.Int32) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.TabItem.get_Icon + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.TabItem.SubscribeToOwnerProperties(Avalonia.AvaloniaObject) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 1d77d3ce44..f42b7c27cb 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/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: diff --git a/samples/ControlCatalog/Assets/Sanctuary/city_bg.jpg b/samples/ControlCatalog/Assets/Sanctuary/city_bg.jpg new file mode 100644 index 0000000000..9a8a4b374f Binary files /dev/null and b/samples/ControlCatalog/Assets/Sanctuary/city_bg.jpg differ diff --git a/samples/ControlCatalog/Assets/Sanctuary/forest_bg.jpg b/samples/ControlCatalog/Assets/Sanctuary/forest_bg.jpg new file mode 100644 index 0000000000..32a360431d Binary files /dev/null and b/samples/ControlCatalog/Assets/Sanctuary/forest_bg.jpg differ diff --git a/samples/ControlCatalog/Assets/Sanctuary/main_arctic_silence.jpg b/samples/ControlCatalog/Assets/Sanctuary/main_arctic_silence.jpg new file mode 100644 index 0000000000..61e7b06212 Binary files /dev/null and b/samples/ControlCatalog/Assets/Sanctuary/main_arctic_silence.jpg differ diff --git a/samples/ControlCatalog/Assets/Sanctuary/main_deep_forest.jpg b/samples/ControlCatalog/Assets/Sanctuary/main_deep_forest.jpg new file mode 100644 index 0000000000..d8576b626f Binary files /dev/null and b/samples/ControlCatalog/Assets/Sanctuary/main_deep_forest.jpg differ diff --git a/samples/ControlCatalog/Assets/Sanctuary/main_desert_sands.jpg b/samples/ControlCatalog/Assets/Sanctuary/main_desert_sands.jpg new file mode 100644 index 0000000000..e4b44477e3 Binary files /dev/null and b/samples/ControlCatalog/Assets/Sanctuary/main_desert_sands.jpg differ diff --git a/samples/ControlCatalog/Assets/Sanctuary/main_hero.jpg b/samples/ControlCatalog/Assets/Sanctuary/main_hero.jpg new file mode 100644 index 0000000000..63dff3f948 Binary files /dev/null and b/samples/ControlCatalog/Assets/Sanctuary/main_hero.jpg differ diff --git a/samples/ControlCatalog/Assets/Sanctuary/mountain_bg.jpg b/samples/ControlCatalog/Assets/Sanctuary/mountain_bg.jpg new file mode 100644 index 0000000000..e9fdd5c0d9 Binary files /dev/null and b/samples/ControlCatalog/Assets/Sanctuary/mountain_bg.jpg differ diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 8304e3e002..2ce24a4d20 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -11,14 +11,7 @@ Designer - - - - - - - - + diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index b6249fe17f..adea1b90fc 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -54,8 +54,15 @@ ScrollViewer.VerticalScrollBarVisibility="Disabled"> - - + + + + + diff --git a/samples/ControlCatalog/Pages/CarouselDemoPage.xaml b/samples/ControlCatalog/Pages/CarouselDemoPage.xaml new file mode 100644 index 0000000000..df4317fcad --- /dev/null +++ b/samples/ControlCatalog/Pages/CarouselDemoPage.xaml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/samples/ControlCatalog/Pages/CarouselDemoPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselDemoPage.xaml.cs new file mode 100644 index 0000000000..36c9961658 --- /dev/null +++ b/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 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); + } + } +} diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml b/samples/ControlCatalog/Pages/CarouselPage.xaml index 352fa32e30..c6e20fec5b 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml @@ -1,44 +1,117 @@ - - An items control that displays its items as pages that fill the control. + + A swipeable items control that can reveal adjacent pages with ViewportFraction. - - - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - Transition - + + + + Transition + None - Slide - Crossfade - 3D Rotation + Page Slide + Cross Fade + Rotate 3D + Card Stack + Wave Reveal + Composite (Slide + Fade) - - - Orientation - + Orientation + Horizontal Vertical + + Viewport Fraction + + + + 1.00 + + + + + + + + + Wrap Selection + Swipe Enabled + + + + + + + + Total Items: + 0 + + + Selected Index: + 0 + + diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs index 713da34051..0a0c973b90 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs +++ b/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; } } } diff --git a/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml b/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml new file mode 100644 index 0000000000..a11a1ea6b8 --- /dev/null +++ b/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml.cs new file mode 100644 index 0000000000..f7c87f56a3 --- /dev/null +++ b/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml.cs @@ -0,0 +1,1068 @@ +using System; +using System.Collections.ObjectModel; +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Styling; +using AvaCarouselPage = Avalonia.Controls.CarouselPage; + +namespace ControlCatalog.Pages; + +public partial class CareCompanionAppPage : UserControl +{ + static readonly Color Primary = Color.Parse("#137fec"); + static readonly Color PrimaryDark = Color.Parse("#0a5bb5"); + static readonly Color PrimaryLight = Color.Parse("#e0f0ff"); + static readonly Color BgLight = Color.Parse("#f6f7f8"); + static readonly Color TextDark = Color.Parse("#111827"); + static readonly Color TextMuted = Color.Parse("#64748b"); + static readonly Color CardBg = Colors.White; + static readonly Color SuccessGreen = Color.Parse("#10b981"); + static readonly Color WarningAmber = Color.Parse("#f59e0b"); + + NavigationPage? _navPage; + AvaCarouselPage? _onboarding; + ScrollViewer? _infoPanel; + + public CareCompanionAppPage() + { + InitializeComponent(); + } + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + _infoPanel = this.FindControl("InfoPanel"); + UpdateInfoVisibility(); + + _navPage = this.FindControl("NavPage"); + if (_navPage == null) return; + + _onboarding = BuildOnboardingCarousel(); + _ = _navPage.PushAsync(_onboarding); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (change.Property == BoundsProperty) + UpdateInfoVisibility(); + } + + void UpdateInfoVisibility() + { + if (_infoPanel != null) + _infoPanel.IsVisible = Bounds.Width >= 650; + } + + static TextBlock Txt(string text, double size, FontWeight weight, Color color, + double opacity = 1, TextAlignment align = TextAlignment.Left, + TextWrapping wrap = TextWrapping.NoWrap) + => new TextBlock + { + Text = text, + FontSize = size, + FontWeight = weight, + Foreground = new SolidColorBrush(color), + Opacity = opacity, + TextAlignment = align, + TextWrapping = wrap, + }; + + static Button StyledButton(object content, IBrush bg, IBrush fg, double height, + CornerRadius radius, Thickness margin = default, double fontSize = 14, + FontWeight fontWeight = FontWeight.SemiBold, + IBrush? border = null, Thickness borderThick = default) + { + var btn = new Button + { + Content = content, + Background = bg, + Foreground = fg, + Height = height, + CornerRadius = radius, + Margin = margin, + Padding = new Thickness(16, 0), + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + BorderBrush = border, + BorderThickness = borderThick, + }; + + var over = new Style(x => x.OfType + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + 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 + + + + + + + + + + + + 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 + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml.cs new file mode 100644 index 0000000000..05a4097a46 --- /dev/null +++ b/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(); + } +} diff --git a/samples/ControlCatalog/Pages/CarouselPage/SanctuaryShowcasePage.xaml b/samples/ControlCatalog/Pages/CarouselPage/SanctuaryShowcasePage.xaml new file mode 100644 index 0000000000..50864e5e57 --- /dev/null +++ b/samples/ControlCatalog/Pages/CarouselPage/SanctuaryShowcasePage.xaml @@ -0,0 +1,335 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CarouselPage/SanctuaryShowcasePage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage/SanctuaryShowcasePage.xaml.cs new file mode 100644 index 0000000000..be91370691 --- /dev/null +++ b/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(); + 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); + } + } +} diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml new file mode 100644 index 0000000000..8dbc44e19b --- /dev/null +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml @@ -0,0 +1,95 @@ + + + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageBreakpointPage.xaml.cs b/samples/ControlCatalog/Pages/DrawerPage/DrawerPageBreakpointPage.xaml.cs new file mode 100644 index 0000000000..0da2eb2ed7 --- /dev/null +++ b/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)"; + } + } +} diff --git a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml b/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml index cba7837314..4987e8979e 100644 --- a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml +++ b/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml @@ -118,6 +118,11 @@ Header="Customization" DrawerLength="260" DrawerHeaderBackground="{DynamicResource SystemControlHighlightAccentBrush}"> + + + + + diff --git a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml.cs b/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml.cs index 697e67f0f4..243bc5868b 100644 --- a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml.cs +++ b/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() + .FirstOrDefault(); + + if (recognizer is not null) + recognizer.IsMouseEnabled = true; + } } } diff --git a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageFirstLookPage.xaml.cs b/samples/ControlCatalog/Pages/DrawerPage/DrawerPageFirstLookPage.xaml.cs index 58a981f640..de72957d73 100644 --- a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageFirstLookPage.xaml.cs +++ b/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() + .FirstOrDefault(); + + if (recognizer is not null) + recognizer.IsMouseEnabled = true; + } } } diff --git a/samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml b/samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml index 22320fbc8d..1e9106ccfe 100644 --- a/samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml +++ b/samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml @@ -52,9 +52,13 @@ - + 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 + + + + + diff --git a/samples/ControlCatalog/Pages/NavigationDemoPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationDemoPage.xaml.cs index b4ec1503bd..d65a43a6ad 100644 --- a/samples/ControlCatalog/Pages/NavigationDemoPage.xaml.cs +++ b/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", diff --git a/samples/ControlCatalog/Pages/NavigationPage/LAvenirAppPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/LAvenirAppPage.xaml.cs index 6c4a67a473..beb0b2dccb 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/LAvenirAppPage.xaml.cs +++ b/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 { 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) diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageAppearancePage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageAppearancePage.xaml.cs index 52e667b0bf..147dbe1f75 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageAppearancePage.xaml.cs +++ b/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); } diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageAttachedMethodsPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageAttachedMethodsPage.xaml.cs index 5a868046f3..01aef5385b 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageAttachedMethodsPage.xaml.cs +++ b/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", diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageBackButtonPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageBackButtonPage.xaml.cs index 347b8e8010..5dd438750f 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageBackButtonPage.xaml.cs +++ b/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}\""); diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml index 74d0e58371..904d4310cc 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml +++ b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml @@ -51,6 +51,13 @@ + + + + + + diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml.cs index faa47f6eda..c1c439f6a4 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageEventsPage.xaml.cs +++ b/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"); diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageFirstLookPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageFirstLookPage.xaml.cs index 32b2e8927d..f9a6f9aa41 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageFirstLookPage.xaml.cs +++ b/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(); } diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageGesturePage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageGesturePage.xaml.cs index ff711f3a63..e185208119 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageGesturePage.xaml.cs +++ b/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() + .FirstOrDefault(); + + if (recognizer is not null) + recognizer.IsMouseEnabled = true; + } } } diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageInteractiveHeaderPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageInteractiveHeaderPage.xaml.cs index 1dc724128b..6a56beabe4 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageInteractiveHeaderPage.xaml.cs +++ b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageInteractiveHeaderPage.xaml.cs @@ -37,6 +37,7 @@ namespace ControlCatalog.Pages ]; private readonly ObservableCollection _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"), diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalPage.xaml.cs index 1dd717234e..81ed6d5c1f 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalPage.xaml.cs +++ b/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); } diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalTransitionsPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalTransitionsPage.xaml.cs index ac4e8c985d..2c77798570 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageModalTransitionsPage.xaml.cs +++ b/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", diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmNavigation.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmNavigation.cs new file mode 100644 index 0000000000..c6262ca9f4 --- /dev/null +++ b/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? 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? 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)); + } + } +} diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmPage.xaml b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmPage.xaml new file mode 100644 index 0000000000..7204b78a4d --- /dev/null +++ b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageMvvmPage.xaml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + +