diff --git a/samples/Avalonia.Labs.Catalog/App.axaml.cs b/samples/Avalonia.Labs.Catalog/App.axaml.cs index 224680c..b81d04a 100644 --- a/samples/Avalonia.Labs.Catalog/App.axaml.cs +++ b/samples/Avalonia.Labs.Catalog/App.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Labs.Catalog.ViewModels; using Avalonia.Labs.Catalog.Views; +using Avalonia.Labs.Controls; namespace Avalonia.Labs.Catalog; @@ -19,17 +20,26 @@ public override void OnFrameworkInitializationCompleted() { desktop.MainWindow = new MainWindow { - DataContext = new MainViewModel() + Content = new PageNavigationHost() + { + Page = new MainView() + { + DataContext = new MainViewModel() + } + } }; } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) { - singleViewPlatform.MainView = new MainView + singleViewPlatform.MainView = new PageNavigationHost() { - DataContext = new MainViewModel() + Page = new MainView() + { + DataContext = new MainViewModel() + } }; } base.OnFrameworkInitializationCompleted(); } -} \ No newline at end of file +} diff --git a/samples/Avalonia.Labs.Catalog/Avalonia.Labs.Catalog.csproj b/samples/Avalonia.Labs.Catalog/Avalonia.Labs.Catalog.csproj index a44dab8..4b4931a 100644 --- a/samples/Avalonia.Labs.Catalog/Avalonia.Labs.Catalog.csproj +++ b/samples/Avalonia.Labs.Catalog/Avalonia.Labs.Catalog.csproj @@ -9,6 +9,11 @@ + + + + + diff --git a/samples/Avalonia.Labs.Catalog/ViewModels/MainViewModel.cs b/samples/Avalonia.Labs.Catalog/ViewModels/MainViewModel.cs index 0914cf8..ba14a1c 100644 --- a/samples/Avalonia.Labs.Catalog/ViewModels/MainViewModel.cs +++ b/samples/Avalonia.Labs.Catalog/ViewModels/MainViewModel.cs @@ -1,4 +1,6 @@ -using Avalonia.Labs.Catalog.Views; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Labs.Catalog.Views; using Avalonia.Labs.Controls; using Avalonia.Styling; using ReactiveUI; @@ -17,6 +19,7 @@ static MainViewModel() private bool? _showNavBar = true; private bool? _showBackButton = true; private INavigationRouter _navigationRouter; + private bool _useSystemSafeArea = true; public MainViewModel() { @@ -36,12 +39,19 @@ public bool? ShowBackButton get => _showBackButton; set => this.RaiseAndSetIfChanged(ref _showBackButton, value); } + public INavigationRouter NavigationRouter { get => _navigationRouter; set => this.RaiseAndSetIfChanged(ref _navigationRouter, value); } + public bool UseSystemSafeArea + { + get => _useSystemSafeArea; + set => this.RaiseAndSetIfChanged(ref _useSystemSafeArea, value); + } + public async void NavigateTo(object page) { if (NavigationRouter != null) diff --git a/samples/Avalonia.Labs.Catalog/Views/MainView.axaml b/samples/Avalonia.Labs.Catalog/Views/MainView.axaml index 1640d01..16624ca 100644 --- a/samples/Avalonia.Labs.Catalog/Views/MainView.axaml +++ b/samples/Avalonia.Labs.Catalog/Views/MainView.axaml @@ -1,53 +1,54 @@ - - + + M 23.935547,4.0019531 C 23.275047,4.0354318 22.748047,4.5831669 22.748047,5.2519531 V 7.7519531 C 22.748047,8.4423031 23.307647,9.0019531 23.998047,9.0019531 24.688347,9.0019531 25.248047,8.4423031 25.248047,7.7519531 V 5.2519531 C 25.248047,4.5615931 24.688347,4.0019531 23.998047,4.0019531 23.976472,4.0019531 23.956853,4.0008732 23.935547,4.0019531 Z M 10.25,9 C 9.9300987,9 9.6093094,9.1231125 9.3652344,9.3671875 8.8770744,9.8553475 8.8770744,10.646666 9.3652344,11.134766 L 11.865234,13.634766 C 12.353334,14.122966 13.146666,14.122966 13.634766,13.634766 14.122966,13.146666 14.122966,12.355287 13.634766,11.867188 L 11.134766,9.3671875 C 10.890716,9.1231125 10.569901,9 10.25,9 Z M 37.75,9 C 37.430087,9 37.111288,9.1231125 36.867188,9.3671875 L 34.367188,11.867188 C 33.879088,12.355287 33.879087,13.146666 34.367188,13.634766 34.855388,14.122966 35.646666,14.122966 36.134766,13.634766 L 38.634766,11.134766 C 39.122966,10.646666 39.122966,9.8553475 38.634766,9.3671875 38.390716,9.1231125 38.069913,9 37.75,9 Z M 24,14 C 18.47715,14 14,18.47715 14,24 14,29.5228 18.47715,34 24,34 29.5228,34 34,29.5228 34,24 34,18.47715 29.5228,14 24,14 Z M 24,15.5 C 28.6944,15.5 32.5,19.30558 32.5,24 32.5,28.6944 28.6944,32.5 24,32.5 Z M 5.25,22.75 C 4.55965,22.75 4,23.3097 4,24 4,24.6904 4.55965,25.25 5.25,25.25 H 7.75 C 8.44035,25.25 9,24.6904 9,24 9,23.3097 8.44035,22.75 7.75,22.75 Z M 40.25,22.75 C 39.5596,22.75 39,23.3097 39,24 39,24.6904 39.5596,25.25 40.25,25.25 H 42.75 C 43.4403,25.25 44,24.6904 44,24 44,23.3097 43.4403,22.75 42.75,22.75 Z M 12.75,34 C 12.4301,34 12.109284,34.123087 11.865234,34.367188 L 9.3652344,36.867188 C 8.8770744,37.355287 8.8770744,38.146666 9.3652344,38.634766 9.8533844,39.122966 10.646666,39.122966 11.134766,38.634766 L 13.634766,36.134766 C 14.122966,35.646666 14.122966,34.855288 13.634766,34.367188 13.390716,34.123087 13.0699,34 12.75,34 Z M 35.25,34 C 34.930087,34 34.611288,34.123087 34.367188,34.367188 33.879088,34.855288 33.879087,35.646666 34.367188,36.134766 L 36.867188,38.634766 C 37.355388,39.122966 38.146666,39.122966 38.634766,38.634766 39.122966,38.146666 39.122966,37.355287 38.634766,36.867188 L 36.134766,34.367188 C 35.890716,34.123087 35.569913,34 35.25,34 Z M 23.998047,39 C 23.307647,39 22.748047,39.5597 22.748047,40.25 V 42.75 C 22.748047,43.4404 23.307647,44 23.998047,44 24.688347,44 25.248047,43.4404 25.248047,42.75 V 40.25 C 25.248047,39.5597 24.688347,39 23.998047,39 Z - - - - - - - - - - - - - - + + - + + diff --git a/samples/Avalonia.Labs.Catalog/Views/MainView.axaml.cs b/samples/Avalonia.Labs.Catalog/Views/MainView.axaml.cs index 4289915..cf3014b 100644 --- a/samples/Avalonia.Labs.Catalog/Views/MainView.axaml.cs +++ b/samples/Avalonia.Labs.Catalog/Views/MainView.axaml.cs @@ -1,10 +1,11 @@ using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Labs.Catalog.ViewModels; +using Avalonia.Labs.Controls; namespace Avalonia.Labs.Catalog.Views; -public partial class MainView : UserControl +public partial class MainView : ContentPage { public MainView() { diff --git a/samples/Avalonia.Labs.Controls.Samples/App.axaml.cs b/samples/Avalonia.Labs.Controls.Samples/App.axaml.cs index fb090eb..5dacd88 100644 --- a/samples/Avalonia.Labs.Controls.Samples/App.axaml.cs +++ b/samples/Avalonia.Labs.Controls.Samples/App.axaml.cs @@ -20,4 +20,4 @@ public override void OnFrameworkInitializationCompleted() base.OnFrameworkInitializationCompleted(); } -} \ No newline at end of file +} diff --git a/samples/Avalonia.Labs.Controls.Samples/Avalonia.Labs.Controls.Samples.csproj b/samples/Avalonia.Labs.Controls.Samples/Avalonia.Labs.Controls.Samples.csproj index d86edb8..6b16675 100644 --- a/samples/Avalonia.Labs.Controls.Samples/Avalonia.Labs.Controls.Samples.csproj +++ b/samples/Avalonia.Labs.Controls.Samples/Avalonia.Labs.Controls.Samples.csproj @@ -13,7 +13,11 @@ - + + + + + diff --git a/src/Avalonia.Labs.Controls/Avalonia.Labs.Controls.csproj b/src/Avalonia.Labs.Controls/Avalonia.Labs.Controls.csproj index 7a75c5c..9023c60 100644 --- a/src/Avalonia.Labs.Controls/Avalonia.Labs.Controls.csproj +++ b/src/Avalonia.Labs.Controls/Avalonia.Labs.Controls.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 enable true True diff --git a/src/Avalonia.Labs.Controls/Page/CarouselPage.cs b/src/Avalonia.Labs.Controls/Page/CarouselPage.cs new file mode 100644 index 0000000..a4fa860 --- /dev/null +++ b/src/Avalonia.Labs.Controls/Page/CarouselPage.cs @@ -0,0 +1,39 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Avalonia.Layout; + +namespace Avalonia.Labs.Controls +{ + public class CarouselPage : SelectingMultiPage + { + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new StackPanel() + { + Orientation = Orientation.Horizontal + }); + + public static readonly StyledProperty> ItemsPanelProperty = + ItemsControl.ItemsPanelProperty.AddOwner(); + + public static readonly StyledProperty OffsetProperty = AvaloniaProperty.Register(nameof(Offset)); + + static CarouselPage() + { + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); + } + + public Vector Offset + { + get => GetValue(OffsetProperty); + set => SetValue(OffsetProperty, value); + } + + public ITemplate ItemsPanel + { + get => GetValue(ItemsPanelProperty); + set => SetValue(ItemsPanelProperty, value); + } + + internal ScrollViewer? ScrollViewerPart { get; private set; } + } +} diff --git a/src/Avalonia.Labs.Controls/Page/ContentPage.cs b/src/Avalonia.Labs.Controls/Page/ContentPage.cs new file mode 100644 index 0000000..8e79842 --- /dev/null +++ b/src/Avalonia.Labs.Controls/Page/ContentPage.cs @@ -0,0 +1,123 @@ +using System; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Layout; +using Avalonia.LogicalTree; +using Avalonia.Metadata; + +namespace Avalonia.Labs.Controls +{ + [TemplatePart("PART_ContentPresenter", typeof(ContentPresenter))] + public class ContentPage : Page + { + public static readonly StyledProperty ContentProperty = + ContentControl.ContentProperty.AddOwner(); + + public static readonly StyledProperty ContentTemplateProperty = + ContentControl.ContentTemplateProperty.AddOwner(); + + public static readonly StyledProperty AutomaticallyApplySafeAreaPaddingProperty = + AvaloniaProperty.Register(nameof(AutomaticallyApplySafeAreaPadding), true); + + public static readonly StyledProperty HorizontalContentAlignmentProperty = + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); + + public static readonly StyledProperty VerticalContentAlignmentProperty = + ContentControl.VerticalContentAlignmentProperty.AddOwner(); + + public HorizontalAlignment HorizontalContentAlignment + { + get { return GetValue(HorizontalContentAlignmentProperty); } + set { SetValue(HorizontalContentAlignmentProperty, value); } + } + + public VerticalAlignment VerticalContentAlignment + { + get { return GetValue(VerticalContentAlignmentProperty); } + set { SetValue(VerticalContentAlignmentProperty, value); } + } + + private ContentPresenter? _contentPresenter; + + [Content] + [DependsOn(nameof(ContentTemplate))] + public object? Content + { + get { return GetValue(ContentProperty); } + set { SetValue(ContentProperty, value); } + } + + public IDataTemplate? ContentTemplate + { + get { return GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } + } + + public bool AutomaticallyApplySafeAreaPadding + { + get { return GetValue(AutomaticallyApplySafeAreaPaddingProperty); } + set { SetValue(AutomaticallyApplySafeAreaPaddingProperty, value); } + } + + static ContentPage() + { + ContentProperty.Changed.AddClassHandler((x, e) => x.ContentChanged(e)); + } + + private void ContentChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.OldValue is ILogical oldChild) + { + LogicalChildren.Remove(oldChild); + } + + if (e.NewValue is ILogical newChild) + { + LogicalChildren.Add(newChild); + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + _contentPresenter = e.NameScope.Get("PART_ContentPresenter"); + + if(_contentPresenter == null ) + { + throw new NullReferenceException("PART_ContentPresenter isn't found the template"); + } + + UpdateContentSafeAreaPadding(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == AutomaticallyApplySafeAreaPaddingProperty) + { + UpdateContentSafeAreaPadding(); + } + else if (change.Property == ContentProperty) + { + UpdateContentSafeAreaPadding(); + } + } + + protected override void UpdateContentSafeAreaPadding() + { + if (_contentPresenter != null) + { + _contentPresenter.Padding = AutomaticallyApplySafeAreaPadding ? Padding.ApplySafeAreaPadding(SafeAreaPadding) : Padding; + _contentPresenter.InvalidateMeasure(); + + if (ActiveChildPage != null) + ActiveChildPage.SafeAreaPadding = Padding.GetRemainingSafeAreaPadding(SafeAreaPadding); + } + } + } +} diff --git a/src/Avalonia.Labs.Controls/Page/DefaultPageDataTemplate.cs b/src/Avalonia.Labs.Controls/Page/DefaultPageDataTemplate.cs new file mode 100644 index 0000000..178e54c --- /dev/null +++ b/src/Avalonia.Labs.Controls/Page/DefaultPageDataTemplate.cs @@ -0,0 +1,41 @@ +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.VisualTree; + +namespace Avalonia.Labs.Controls +{ + internal class DefaultPageDataTemplate : IRecyclingDataTemplate + { + public Control? Build(object? param) + { + if(param is Page page) + return page; + if(param is Control control) + { + var visualParent = control.GetVisualParent() as ContentPresenter; + if (visualParent != null) + visualParent.Content = null; + } + return new ContentPage() + { + Content = param + }; + } + + public Control? Build(object? data, Control? existing) + { + if(existing != null) + { + + } + + return Build(data); + } + + public bool Match(object? data) + { + return data is not null; + } + } +} diff --git a/src/Avalonia.Labs.Controls/Page/INavigation.cs b/src/Avalonia.Labs.Controls/Page/INavigation.cs new file mode 100644 index 0000000..aee3b0e --- /dev/null +++ b/src/Avalonia.Labs.Controls/Page/INavigation.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Labs.Controls +{ + public interface INavigation + { + object? Pop(); + void Push(object page); + } +} diff --git a/src/Avalonia.Labs.Controls/Page/MasterDetailPage.cs b/src/Avalonia.Labs.Controls/Page/MasterDetailPage.cs new file mode 100644 index 0000000..c06cf02 --- /dev/null +++ b/src/Avalonia.Labs.Controls/Page/MasterDetailPage.cs @@ -0,0 +1,181 @@ +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Layout; +using Avalonia.LogicalTree; +using Avalonia.Metadata; + +namespace Avalonia.Labs.Controls +{ + [TemplatePart("PART_MasterPresenter", typeof(ContentPresenter))] + [TemplatePart("PART_DetailPresenter", typeof(ContentPresenter))] + public class MasterDetailPage : Page + { + public static readonly StyledProperty MasterProperty = + AvaloniaProperty.Register(nameof(Master)); + + public static readonly StyledProperty MasterTemplateProperty = + AvaloniaProperty.Register(nameof(MasterTemplate), new DefaultPageDataTemplate()); + + public static readonly StyledProperty DetailTemplateProperty = + AvaloniaProperty.Register(nameof(DetailTemplate), new DefaultPageDataTemplate()); + + public static readonly StyledProperty DetailProperty = + AvaloniaProperty.Register(nameof(Detail)); + + public static readonly StyledProperty IsPresentedProperty = + AvaloniaProperty.Register(nameof(IsPresented)); + + public static readonly StyledProperty HorizontalContentAlignmentProperty = + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); + + public static readonly StyledProperty VerticalContentAlignmentProperty = + ContentControl.VerticalContentAlignmentProperty.AddOwner(); + + public static readonly StyledProperty DisplayModeProperty = + SplitView.DisplayModeProperty.AddOwner(); + + private ContentPresenter? _detailPresenter; + private ContentPresenter? _masterPresenter; + private Border? _topBar; + + public HorizontalAlignment HorizontalContentAlignment + { + get { return GetValue(HorizontalContentAlignmentProperty); } + set { SetValue(HorizontalContentAlignmentProperty, value); } + } + + public VerticalAlignment VerticalContentAlignment + { + get { return GetValue(VerticalContentAlignmentProperty); } + set { SetValue(VerticalContentAlignmentProperty, value); } + } + + [Content] + [DependsOn(nameof(MasterTemplate))] + public object? Master + { + get => GetValue(MasterProperty); + set => SetValue(MasterProperty, value); + } + + [DependsOn(nameof(DetailTemplate))] + public object? Detail + { + get => GetValue(DetailProperty); + set => SetValue(DetailProperty, value); + } + + public IDataTemplate? MasterTemplate + { + get => GetValue(MasterTemplateProperty); + set => SetValue(MasterTemplateProperty, value); + } + + public IDataTemplate? DetailTemplate + { + get => GetValue(DetailTemplateProperty); + set => SetValue(DetailTemplateProperty, value); + } + + public bool IsPresented + { + get => GetValue(IsPresentedProperty); + set => SetValue(IsPresentedProperty, value); + } + public SplitViewDisplayMode DisplayMode + { + get => GetValue(DisplayModeProperty); + set => SetValue(DisplayModeProperty, value); + } + + static MasterDetailPage() + { + PageNavigationSystemBackButtonPressedEvent.AddClassHandler((sender, eventArgs) => + { + if (sender.IsPresented) + { + sender.IsPresented = false; + + eventArgs.Handled = true; + } + }); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + _detailPresenter = e.NameScope.Get("PART_DetailPresenter"); + _masterPresenter = e.NameScope.Get("PART_MasterPresenter"); + _topBar = e.NameScope.Get("PART_TopBar"); + + UpdateContentSafeAreaPadding(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == MasterProperty || change.Property == DetailProperty) + { + if (change.OldValue is ILogical oldLogical) + { + LogicalChildren.Remove(oldLogical); + } + if (change.NewValue is ILogical newLogical) + { + LogicalChildren.Add(newLogical); + } + UpdateActivePage(); + } + if(change.Property == IsPresentedProperty || change.Property == DisplayModeProperty) + { + UpdateActivePage(); + } + } + + protected override void UpdateContentSafeAreaPadding() + { + if (_detailPresenter != null && _masterPresenter != null) + { + _masterPresenter.Padding = new Thickness( + SafeAreaPadding.Left, + SafeAreaPadding.Top, + 0, + SafeAreaPadding.Bottom); + + if (_topBar != null) + { + var navPadding = SafeAreaPadding; + _topBar.Margin = new Thickness(navPadding.Left, navPadding.Top, navPadding.Right, 0); + } + + _detailPresenter.Padding = Padding; + + if (_detailPresenter.Child is Page detail) + { + var remainingSafeArea = Padding.GetRemainingSafeAreaPadding(SafeAreaPadding); + detail.SafeAreaPadding = new Thickness(remainingSafeArea.Left, 0, remainingSafeArea.Right, remainingSafeArea.Bottom); + } + + InvalidateMeasure(); + } + } + + protected override void UpdateActivePage() + { + if (_masterPresenter != null && _detailPresenter != null) + { + if (IsPresented && (DisplayMode == SplitViewDisplayMode.Overlay || DisplayMode == SplitViewDisplayMode.CompactOverlay)) + ActiveChildPage = _masterPresenter.Child as Page; + else + { + ActiveChildPage = _detailPresenter.Child as Page; + } + } + } + } +} diff --git a/src/Avalonia.Labs.Controls/Page/MultiPage.cs b/src/Avalonia.Labs.Controls/Page/MultiPage.cs new file mode 100644 index 0000000..bf54b23 --- /dev/null +++ b/src/Avalonia.Labs.Controls/Page/MultiPage.cs @@ -0,0 +1,86 @@ +using System.Collections; +using System.Collections.Specialized; +using Avalonia.Controls.Templates; +using Avalonia.LogicalTree; + +namespace Avalonia.Labs.Controls +{ + public abstract class MultiPage : Page + { + public static readonly DirectProperty PagesProperty = + AvaloniaProperty.RegisterDirect(nameof(Pages), + o => o.Pages, (o, v) => o.Pages = v); + + public static readonly StyledProperty PageTemplateProperty = + AvaloniaProperty.Register(nameof(PageTemplate), new DefaultPageDataTemplate()); + + private IEnumerable? _pages; + + public virtual IEnumerable? Pages + { + get => _pages; + set + { + _pages = value; + SetAndRaise(PagesProperty, ref _pages, value); + } + } + + public IDataTemplate? PageTemplate + { + get => GetValue(PageTemplateProperty); + set => SetValue(PageTemplateProperty, value); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == PagesProperty) + { + if (change.OldValue is INotifyCollectionChanged oldNotifyCollection) + { + oldNotifyCollection.CollectionChanged -= NotifyCollection_CollectionChanged; + } + + LogicalChildren.Clear(); + + if (change.NewValue != null) + { + foreach (var page in Pages!) + { + if (page is ILogical logical) + { + LogicalChildren.Add(logical); + } + } + } + + if (change.NewValue is INotifyCollectionChanged newNotifyCollection) + { + newNotifyCollection.CollectionChanged += NotifyCollection_CollectionChanged; + } + + UpdateActivePage(); + } + } + + private void NotifyCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + foreach (var old in e.OldItems) + { + if (old is ILogical logical) + LogicalChildren.Remove(logical); + } + + if (e.NewItems != null) + foreach (var newItem in e.NewItems) + { + if (newItem is ILogical logical) + LogicalChildren.Add(logical); + } + UpdateActivePage(); + } + } +} diff --git a/src/Avalonia.Labs.Controls/Page/NavigationPage.cs b/src/Avalonia.Labs.Controls/Page/NavigationPage.cs new file mode 100644 index 0000000..c319309 --- /dev/null +++ b/src/Avalonia.Labs.Controls/Page/NavigationPage.cs @@ -0,0 +1,287 @@ +using System.Collections; +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Media; +using Avalonia.Metadata; + +namespace Avalonia.Labs.Controls +{ + /// + /// A navigation page that supports simple stack-based navigation + /// + [TemplatePart("PART_NavigationBar", typeof(Border))] + [TemplatePart("PART_BackButton", typeof(Button))] + [TemplatePart("PART_ForwardButton", typeof(Button))] + [TemplatePart("PART_ContentPresenter", typeof(TransitioningContentControl))] + public class NavigationPage : MultiPage, INavigation + { + private Button? _backButton; + + internal static readonly StyledProperty ContentProperty = + ContentControl.ContentProperty.AddOwner(); + + public static readonly StyledProperty BarBackgroundProperty = + AvaloniaProperty.Register(nameof(BarBackground)); + + public static readonly StyledProperty BarTextColorProperty = + AvaloniaProperty.Register(nameof(BarTextColor)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsBackButtonVisibleProperty = + AvaloniaProperty.Register(nameof(IsBackButtonVisible), true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsNavBarVisibleProperty = + AvaloniaProperty.Register(nameof(IsNavBarVisible), true); + + public static readonly AttachedProperty BackButtonTitleProperty = + AvaloniaProperty.RegisterAttached("BackButtonTitle"); + + public static readonly AttachedProperty HasBackButtonProperty = + AvaloniaProperty.RegisterAttached("HasBackButton", true); + + public static readonly AttachedProperty TitleViewProperty = + AvaloniaProperty.RegisterAttached("TitleView"); + + public static readonly AttachedProperty TitleIconProperty = + AvaloniaProperty.RegisterAttached("TitleIcon"); + + public IBrush? BarBackground + { + get { return GetValue(BarBackgroundProperty); } + set { SetValue(BarBackgroundProperty, value); } + } + public IBrush? BarTextColor + { + get { return GetValue(BarTextColorProperty); } + set { SetValue(BarTextColorProperty, value); } + } + + public bool? IsBackButtonVisible + { + get => ActiveChildPage != null && GetValue(IsBackButtonVisibleProperty) == true && GetHasBackButton(ActiveChildPage); + set => SetValue(IsBackButtonVisibleProperty, value); + } + + [Content] + [DependsOn(nameof(PageTemplate))] + internal object? Content + { + get { return GetValue(ContentProperty); } + set { SetValue(ContentProperty, value); } + } + + /// + /// Gets or sets the visibility of the navigation bar + /// + public bool? IsNavBarVisible + { + get => GetValue(IsNavBarVisibleProperty); + set => SetValue(IsNavBarVisibleProperty, value); + } + + public static string? GetBackButtonTitle(Page page) + { + return page.GetValue(BackButtonTitleProperty); + } + + public static void SetBackButtonTitle(Page page, string title) + { + page.SetValue(BackButtonTitleProperty, title); + } + + public static bool GetHasBackButton(Page page) + { + return page.GetValue(HasBackButtonProperty); + } + + public static void SetHasBackButton(Page page, bool hasBackButton) + { + page.SetValue(BackButtonTitleProperty, hasBackButton); + } + + public static object? GetTitleView(Page page) + { + return page.GetValue(TitleViewProperty); + } + + public static void SetTitleView(Page page, object? titleView) + { + page.SetValue(TitleViewProperty, titleView); + } + + public static IImage? GetTitleIcon(Page page) + { + return page.GetValue(TitleIconProperty); + } + + public static void SetTitleIcon(Page page, IImage titleIcon) + { + page.SetValue(TitleIconProperty, titleIcon); + } + + static NavigationPage() + { + PageNavigationSystemBackButtonPressedEvent.AddClassHandler( (sender, eventArgs) => + { + if(sender.IsBackButtonVisible == true) + { + eventArgs.Handled = sender.Pop() != null; + } + }); + } + + public NavigationPage() + { + Pages = new Stack(); + } + + /// + /// Gets or sets the BackButton template part. + /// + private Button? BackButton + { + get { return _backButton; } + set + { + if (_backButton != null) + { + _backButton.Click -= BackButton_Clicked; + } + _backButton = value; + if (_backButton != null) + { + _backButton.Click += BackButton_Clicked; + } + } + } + + private ContentPresenter? _contentPresenter; + private Border? _navBar; + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + BackButton = e.NameScope.Get